mirror of
https://github.com/kd2org/picodav.git
synced 2024-09-30 12:11:30 +02:00
Compare commits
No commits in common. "main" and "0.1.3" have entirely different histories.
1
Makefile
1
Makefile
@ -1,5 +1,4 @@
|
|||||||
all: clean index.php
|
all: clean index.php
|
||||||
KD2FW_URL := https://fossil.kd2.org/kd2fw/doc/trunk/src/lib/KD2/
|
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
@-mkdir -p lib/KD2/WebDAV
|
@-mkdir -p lib/KD2/WebDAV
|
||||||
|
17
README.md
17
README.md
@ -21,23 +21,22 @@ If you drop the [`index.php`](https://github.com/kd2org/picodav/raw/main/index.p
|
|||||||
* Download all files of a directory
|
* Download all files of a directory
|
||||||
* Manage users and password with only a text file!
|
* Manage users and password with only a text file!
|
||||||
* Restrict users to some directories, control where they can write!
|
* Restrict users to some directories, control where they can write!
|
||||||
* Support for [rclone](https://rclone.org) as a NextCloud provider
|
|
||||||
|
|
||||||
## WebDAV clients
|
## WebDAV clients
|
||||||
|
|
||||||
You can use any WebDAV client, but we recommend these:
|
You can use any WebDAV client, but we recommend these:
|
||||||
|
|
||||||
* Windows/OSX: [CyberDuck](https://cyberduck.io/download/)
|
* Windows/OSX: [CyberDuck](https://cyberduck.io/download/)
|
||||||
* Linux: Any file manager should be able to connect to WebDAV (Dolphin, Thunar, Nautilus, etc.), but you can also use [FUSE webdavfs](https://github.com/miquels/webdavfs), or [rclone](https://rclone.org)
|
* Linux: Any file manager should be able to connect to WebDAV (Dolphin, Thunar, Nautilus, etc.), but you can also use [FUSE webdavfs](https://github.com/miquels/webdavfs)
|
||||||
* Android: [RCX](https://f-droid.org/en/packages/io.github.x0b.rcx/) and [DAVx⁵](https://www.davx5.com/), see [the manual](https://manual.davx5.com/webdav_mounts.html)
|
* Android: [RCX](https://f-droid.org/en/packages/io.github.x0b.rcx/) and [DAVx⁵](https://www.davx5.com/), see [the manual](https://manual.davx5.com/webdav_mounts.html)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
It's really as simple as it says: just upload the [`index.php`](https://github.com/kd2org/picodav/raw/main/index.php) file to a directory on your web-server, and it will now be available via WebDAV!
|
It's really as simple as it says: just upload the [`index.php`](https://github.com/kd2org/picodav/raw/main/index.php) file to a directory on your web-server, and it will now be available via WebDAV!
|
||||||
|
|
||||||
If you are using Apache (version 2.3.9 or later is required), a .htaccess file will be created if it does not exist, to redirect requests to `index.php`. If not, you can use the provided `.htaccess` as a basis for your server configuration.
|
If you are using Apache (version 2.3.9 or later is required), a .htaccess file will be created if it does not exist, to redirect requests to `index.php`. If not, you can use
|
||||||
|
|
||||||
The only requirement is PHP 7.4, or more recent (8.0-8.2 are also supported).
|
The only requirement is PHP 7.4, or more recent (8.0 and 8.1 are also supported, not tested with PHP 8.2).
|
||||||
|
|
||||||
Note that by default, write access is disabled for security purposes. See below to enable write access.
|
Note that by default, write access is disabled for security purposes. See below to enable write access.
|
||||||
|
|
||||||
@ -47,12 +46,6 @@ PicoDAV accepts a configuration file named `.picodav.ini`.
|
|||||||
|
|
||||||
It should be in the same directory as `index.php`.
|
It should be in the same directory as `index.php`.
|
||||||
|
|
||||||
It accepts these options:
|
|
||||||
|
|
||||||
* `ANONYMOUS_READ` (boolean, see below)
|
|
||||||
* `ANONYMOUS_WRITE` (boolean, see below)
|
|
||||||
* `HTTP_LOG_FILE` (string, set to a file path to log HTTP requests for debug purposes)
|
|
||||||
|
|
||||||
### Users and passwords
|
### Users and passwords
|
||||||
|
|
||||||
By default, the WebDAV server is accessible to everyone.
|
By default, the WebDAV server is accessible to everyone.
|
||||||
@ -71,9 +64,9 @@ password = verySecret
|
|||||||
write = true
|
write = true
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that PicoDAV will replace this password with a hashed version the next time it is accessed, don't worry about that, this is for extra safety, just in case the `.picodav.ini` is accessed by a hacker if you made mistake in your web server configuration.
|
Note that PicoDAV will replace this password with an encrypted version the next time it is accessed, don't worry about that, this is for extra safety, just in case the `.picodav.ini` is accessed by a hacker if you made mistake in your web server configuration.
|
||||||
|
|
||||||
Here is an example of the password once it has been hashed:
|
Here is an example of the password once it has been encrypted:
|
||||||
|
|
||||||
```
|
```
|
||||||
password = '$2y$10$fbdabTjNPN3gMAUlaSEoR.kKHLnh0yMGneuJ7P2AOhSSNr8gUaCPu'
|
password = '$2y$10$fbdabTjNPN3gMAUlaSEoR.kKHLnh0yMGneuJ7P2AOhSSNr8gUaCPu'
|
||||||
|
366
index.php
366
index.php
@ -25,16 +25,6 @@ namespace KD2\WebDAV
|
|||||||
'DAV::quota-available-bytes',
|
'DAV::quota-available-bytes',
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROP_NAMESPACE_MICROSOFT = 'urn:schemas-microsoft-com:';
|
|
||||||
|
|
||||||
const MODIFICATION_TIME_PROPERTIES = [
|
|
||||||
'DAV::lastmodified',
|
|
||||||
'DAV::creationdate',
|
|
||||||
'DAV::getlastmodified',
|
|
||||||
'urn:schemas-microsoft-com::Win32LastModifiedTime',
|
|
||||||
'urn:schemas-microsoft-com::Win32CreationTime',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Custom properties
|
// Custom properties
|
||||||
|
|
||||||
const PROP_DIGEST_MD5 = 'urn:karadav:digest_md5';
|
const PROP_DIGEST_MD5 = 'urn:karadav:digest_md5';
|
||||||
@ -44,8 +34,6 @@ namespace KD2\WebDAV
|
|||||||
const SHARED_LOCK = 'shared';
|
const SHARED_LOCK = 'shared';
|
||||||
const EXCLUSIVE_LOCK = 'exclusive';
|
const EXCLUSIVE_LOCK = 'exclusive';
|
||||||
|
|
||||||
protected bool $enable_gzip = true;
|
|
||||||
|
|
||||||
protected string $base_uri;
|
protected string $base_uri;
|
||||||
|
|
||||||
public string $original_uri;
|
public string $original_uri;
|
||||||
@ -66,18 +54,7 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
public function setBaseURI(string $uri): void
|
public function setBaseURI(string $uri): void
|
||||||
{
|
{
|
||||||
$this->base_uri = '/' . ltrim($uri, '/');
|
$this->base_uri = rtrim($uri, '/') . '/';
|
||||||
$this->base_uri = rtrim($this->base_uri, '/') . '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function extendExecutionTime(): void
|
|
||||||
{
|
|
||||||
if (false === strpos(@ini_get('disable_functions'), 'set_time_limit')) {
|
|
||||||
@set_time_limit(3600);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ini_set('max_execution_time', '3600');
|
|
||||||
@ini_set('max_input_time', '3600');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _prefix(string $uri): string
|
protected function _prefix(string $uri): string
|
||||||
@ -115,7 +92,7 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
foreach ($list as $file => $props) {
|
foreach ($list as $file => $props) {
|
||||||
if (null === $props) {
|
if (null === $props) {
|
||||||
$props = $this->storage->propfind(trim($uri . '/' . $file, '/'), self::BASIC_PROPERTIES, 0);
|
$props = $this->storage->properties(trim($uri . '/' . $file, '/'), self::BASIC_PROPERTIES, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
$collection = !empty($props['DAV::resourcetype']) && $props['DAV::resourcetype'] == 'collection';
|
$collection = !empty($props['DAV::resourcetype']) && $props['DAV::resourcetype'] == 'collection';
|
||||||
@ -207,20 +184,11 @@ namespace KD2\WebDAV
|
|||||||
}
|
}
|
||||||
|
|
||||||
$hash = null;
|
$hash = null;
|
||||||
$hash_algo = null;
|
|
||||||
|
|
||||||
// Support for checksum matching
|
// Support for checksum matching
|
||||||
// https://dcache.org/old/manuals/UserGuide-6.0/webdav.shtml#checksums
|
// https://dcache.org/old/manuals/UserGuide-6.0/webdav.shtml#checksums
|
||||||
if (!empty($_SERVER['HTTP_CONTENT_MD5'])) {
|
if (!empty($_SERVER['HTTP_CONTENT_MD5'])) {
|
||||||
$hash = bin2hex(base64_decode($_SERVER['HTTP_CONTENT_MD5']));
|
$hash = bin2hex(base64_decode($_SERVER['HTTP_CONTENT_MD5']));
|
||||||
$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)) {
|
|
||||||
$hash_algo = strtok($match[0], ':');
|
|
||||||
$hash = strtok('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = $this->_prefix($uri);
|
$uri = $this->_prefix($uri);
|
||||||
@ -229,47 +197,23 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
if (!empty($_SERVER['HTTP_IF_MATCH'])) {
|
if (!empty($_SERVER['HTTP_IF_MATCH'])) {
|
||||||
$etag = trim($_SERVER['HTTP_IF_MATCH'], '" ');
|
$etag = trim($_SERVER['HTTP_IF_MATCH'], '" ');
|
||||||
$prop = $this->storage->propfind($uri, ['DAV::getetag'], 0);
|
$prop = $this->storage->properties($uri, ['DAV::getetag'], 0);
|
||||||
|
|
||||||
if (!empty($prop['DAV::getetag']) && $prop['DAV::getetag'] != $etag) {
|
if (!empty($prop['DAV::getetag']) && $prop['DAV::getetag'] != $etag) {
|
||||||
throw new Exception('ETag did not match condition', 412);
|
throw new Exception('ETag did not match condition', 412);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific to NextCloud/ownCloud, to allow setting file mtime
|
// Specific to NextCloud/ownCloud
|
||||||
// This expects a UNIX timestamp
|
|
||||||
$mtime = (int)($_SERVER['HTTP_X_OC_MTIME'] ?? 0) ?: null;
|
$mtime = (int)($_SERVER['HTTP_X_OC_MTIME'] ?? 0) ?: null;
|
||||||
|
|
||||||
$this->extendExecutionTime();
|
|
||||||
|
|
||||||
$stream = fopen('php://input', 'r');
|
|
||||||
|
|
||||||
// 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') {
|
|
||||||
// We can't seek here
|
|
||||||
// see https://github.com/php/php-src/issues/9441
|
|
||||||
$l = strlen(fread($stream, 1));
|
|
||||||
|
|
||||||
if ($l === 0) {
|
|
||||||
throw new Exception('This server cannot accept "Transfer-Encoding: chunked" uploads (please upgrade to mod_fcgid >= 2.3.10).', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset stream
|
|
||||||
fseek($stream, 0, SEEK_SET);
|
|
||||||
}
|
|
||||||
|
|
||||||
$created = $this->storage->put($uri, $stream, $hash_algo, $hash);
|
|
||||||
|
|
||||||
if ($mtime) {
|
if ($mtime) {
|
||||||
$mtime = new \DateTime('@' . $mtime);
|
|
||||||
|
|
||||||
if ($this->storage->touch($uri, $mtime)) {
|
|
||||||
header('X-OC-MTime: accepted');
|
header('X-OC-MTime: accepted');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$prop = $this->storage->propfind($uri, ['DAV::getetag'], 0);
|
$created = $this->storage->put($uri, fopen('php://input', 'r'), $hash, $mtime);
|
||||||
|
|
||||||
|
$prop = $this->storage->properties($uri, ['DAV::getetag'], 0);
|
||||||
|
|
||||||
if (!empty($prop['DAV::getetag'])) {
|
if (!empty($prop['DAV::getetag'])) {
|
||||||
$value = $prop['DAV::getetag'];
|
$value = $prop['DAV::getetag'];
|
||||||
@ -297,7 +241,7 @@ namespace KD2\WebDAV
|
|||||||
$requested_props[] = self::PROP_DIGEST_MD5;
|
$requested_props[] = self::PROP_DIGEST_MD5;
|
||||||
}
|
}
|
||||||
|
|
||||||
$props = $this->storage->propfind($uri, $requested_props, 0);
|
$props = $this->storage->properties($uri, $requested_props, 0);
|
||||||
|
|
||||||
if (!$props) {
|
if (!$props) {
|
||||||
throw new Exception('Resource Not Found', 404);
|
throw new Exception('Resource Not Found', 404);
|
||||||
@ -378,11 +322,9 @@ namespace KD2\WebDAV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($file['content']) && !isset($file['resource']) && !isset($file['path'])) {
|
if (!isset($file['content']) && !isset($file['resource']) && !isset($file['path'])) {
|
||||||
throw new \RuntimeException('Invalid file array returned by ::get(): ' . print_r($file, true));
|
throw new \RuntimeException('Invalid file array returned by ::get()');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->extendExecutionTime();
|
|
||||||
|
|
||||||
$length = $start = $end = null;
|
$length = $start = $end = null;
|
||||||
$gzip = false;
|
$gzip = false;
|
||||||
|
|
||||||
@ -402,20 +344,17 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
$this->log('HTTP Range requested: %s-%s', $start, $end);
|
$this->log('HTTP Range requested: %s-%s', $start, $end);
|
||||||
}
|
}
|
||||||
elseif ($this->enable_gzip
|
elseif (isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|
||||||
&& isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|
|
||||||
&& false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')
|
&& 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
|
// 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)) {
|
&& !preg_match('/\.(?:mp4|m4a|zip|docx|xlsx|ods|odt|odp|7z|gz|bz2|rar|webm|ogg|mp3|ogm|flac|ogv|mkv|avi)$/i', $uri)) {
|
||||||
$gzip = true;
|
$gzip = true;
|
||||||
header('Content-Encoding: gzip', true);
|
header('Content-Encoding: gzip', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to avoid common issues with output buffering and stuff
|
// Try to avoid common issues with output buffering and stuff
|
||||||
if (function_exists('apache_setenv')) {
|
if (function_exists('apache_setenv'))
|
||||||
|
{
|
||||||
@apache_setenv('no-gzip', 1);
|
@apache_setenv('no-gzip', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,9 +436,9 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
if ($gzip) {
|
if ($gzip) {
|
||||||
$this->log('Using gzip output compression');
|
$this->log('Using gzip output compression');
|
||||||
$gzip = deflate_init(ZLIB_ENCODING_GZIP);
|
$gzip = deflate_init(ZLIB_ENCODING_GZIP, ['level' => 9]);
|
||||||
|
|
||||||
$fp = fopen('php://temp', 'wb');
|
$fp = fopen('php://memory', 'wb');
|
||||||
|
|
||||||
while (!feof($file['resource'])) {
|
while (!feof($file['resource'])) {
|
||||||
fwrite($fp, deflate_add($gzip, fread($file['resource'], 8192), ZLIB_NO_FLUSH));
|
fwrite($fp, deflate_add($gzip, fread($file['resource'], 8192), ZLIB_NO_FLUSH));
|
||||||
@ -519,16 +458,14 @@ namespace KD2\WebDAV
|
|||||||
header('Content-Length: ' . $length, true);
|
header('Content-Length: ' . $length, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$block_size = 8192*4;
|
|
||||||
|
|
||||||
while (!feof($file['resource']) && ($end === null || $end > 0)) {
|
while (!feof($file['resource']) && ($end === null || $end > 0)) {
|
||||||
$l = $end !== null ? min($block_size, $end) : $block_size;
|
$l = $end !== null ? min(8192, $end) : 8192;
|
||||||
|
|
||||||
echo fread($file['resource'], $l);
|
echo fread($file['resource'], $l);
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
if (null !== $end) {
|
if (null !== $end) {
|
||||||
$end -= $block_size;
|
$end -= 8192;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,7 +525,7 @@ namespace KD2\WebDAV
|
|||||||
// should do just nothing, see 'depth_zero_copy' test in litmus
|
// should do just nothing, see 'depth_zero_copy' test in litmus
|
||||||
if ($depth == 0
|
if ($depth == 0
|
||||||
&& $this->storage->exists($destination)
|
&& $this->storage->exists($destination)
|
||||||
&& current($this->storage->propfind($destination, ['DAV::resourcetype'], 0)) == 'collection') {
|
&& current($this->storage->properties($destination, ['DAV::resourcetype'], 0)) == 'collection') {
|
||||||
$overwritten = $this->storage->exists($uri);
|
$overwritten = $this->storage->exists($uri);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -707,24 +644,18 @@ namespace KD2\WebDAV
|
|||||||
$requested_keys = $requested ? array_keys($requested) : null;
|
$requested_keys = $requested ? array_keys($requested) : null;
|
||||||
|
|
||||||
// Find root element properties
|
// Find root element properties
|
||||||
$properties = $this->storage->propfind($uri, $requested_keys, $depth);
|
$properties = $this->storage->properties($uri, $requested_keys, $depth);
|
||||||
|
|
||||||
if (null === $properties) {
|
if (null === $properties) {
|
||||||
throw new Exception('This does not exist', 404);
|
throw new Exception('This does not exist', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($properties['DAV::getlastmodified'])) {
|
|
||||||
foreach (self::MODIFICATION_TIME_PROPERTIES as $name) {
|
|
||||||
$properties[$name] = $properties['DAV::getlastmodified'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$items = [$uri => $properties];
|
$items = [$uri => $properties];
|
||||||
|
|
||||||
if ($depth) {
|
if ($depth) {
|
||||||
foreach ($this->storage->list($uri, $requested) as $file => $properties) {
|
foreach ($this->storage->list($uri, $requested) as $file => $properties) {
|
||||||
$path = trim($uri . '/' . $file, '/');
|
$path = trim($uri . '/' . $file, '/');
|
||||||
$properties = $properties ?? $this->storage->propfind($path, $requested_keys, 0);
|
$properties = $properties ?? $this->storage->properties($path, $requested_keys, 0);
|
||||||
|
|
||||||
if (!$properties) {
|
if (!$properties) {
|
||||||
$this->log('!!! Cannot find "%s"', $path);
|
$this->log('!!! Cannot find "%s"', $path);
|
||||||
@ -968,93 +899,19 @@ namespace KD2\WebDAV
|
|||||||
$uri = $this->_prefix($uri);
|
$uri = $this->_prefix($uri);
|
||||||
$this->checkLock($uri);
|
$this->checkLock($uri);
|
||||||
|
|
||||||
$prefix = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
|
||||||
$prefix.= '<d:multistatus xmlns:d="DAV:"';
|
|
||||||
$suffix = "</d:multistatus>\n";
|
|
||||||
|
|
||||||
$body = file_get_contents('php://input');
|
$body = file_get_contents('php://input');
|
||||||
|
|
||||||
$properties = $this->parsePropPatch($body);
|
$this->storage->setProperties($uri, $body);
|
||||||
$root_namespaces = [];
|
|
||||||
$i = 0;
|
|
||||||
$set_time = null;
|
|
||||||
$set_time_name = null;
|
|
||||||
|
|
||||||
foreach ($properties as $name => $value) {
|
|
||||||
$pos = strrpos($name, ':');
|
|
||||||
$ns = substr($name, 0, $pos);
|
|
||||||
|
|
||||||
if (!array_key_exists($ns, $root_namespaces)) {
|
|
||||||
$alias = 'rns' . $i++;
|
|
||||||
$root_namespaces[$ns] = $alias;
|
|
||||||
$prefix .= sprintf(' xmlns:%s="%s"', $alias, htmlspecialchars($ns, ENT_XML1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if the client wants to set the modification time
|
|
||||||
foreach (self::MODIFICATION_TIME_PROPERTIES as $name) {
|
|
||||||
if (!array_key_exists($name, $properties) || $value['action'] !== 'set' || empty($value['content'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ts = $value['content'];
|
|
||||||
|
|
||||||
if (ctype_digit($ts)) {
|
|
||||||
$ts = '@' . $ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
$set_time = new \DateTime($value['content']);
|
|
||||||
$set_time_name = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
$prefix .= sprintf(">\n<d:response>\n <d:href>%s</d:href>\n", htmlspecialchars($url, ENT_XML1));
|
|
||||||
|
|
||||||
// http_response_code doesn't know the 207 status code
|
// http_response_code doesn't know the 207 status code
|
||||||
header('HTTP/1.1 207 Multi-Status', true);
|
header('HTTP/1.1 207 Multi-Status', true);
|
||||||
header('Content-Type: application/xml; charset=utf-8', true);
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
|
|
||||||
if (!count($properties)) {
|
$out = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
||||||
return $prefix . $suffix;
|
$out .= '<d:multistatus xmlns:d="DAV:">';
|
||||||
}
|
$out .= '</d:multistatus>';
|
||||||
|
|
||||||
if ($set_time) {
|
return $out;
|
||||||
unset($properties[$set_time_name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$return = $this->storage->proppatch($uri, $properties);
|
|
||||||
|
|
||||||
if ($set_time && $this->touch($uri, $set_time)) {
|
|
||||||
$return[$set_time_name] = 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
$out = '';
|
|
||||||
|
|
||||||
static $messages = [
|
|
||||||
200 => 'OK',
|
|
||||||
403 => 'Forbidden',
|
|
||||||
409 => 'Conflict',
|
|
||||||
427 => 'Failed Dependency',
|
|
||||||
507 => 'Insufficient Storage',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($return as $name => $status) {
|
|
||||||
$pos = strrpos($name, ':');
|
|
||||||
$ns = substr($name, 0, $pos);
|
|
||||||
$name = substr($name, $pos + 1);
|
|
||||||
|
|
||||||
$out .= " <d:propstat>\n <d:prop>";
|
|
||||||
$out .= sprintf("<%s:%s /></d:prop>\n <d:status>HTTP/1.1 %d %s</d:status>",
|
|
||||||
$root_namespaces[$ns],
|
|
||||||
$name,
|
|
||||||
$status,
|
|
||||||
$messages[$status] ?? ''
|
|
||||||
);
|
|
||||||
$out .= "\n </d:propstat>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$out .= "</d:response>\n";
|
|
||||||
|
|
||||||
return $prefix . $out . $suffix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function http_lock(string $uri): ?string
|
public function http_lock(string $uri): ?string
|
||||||
@ -1205,7 +1062,7 @@ namespace KD2\WebDAV
|
|||||||
&& preg_match('/\(<([^>]*)>\s+\["([^""]+)"\]\)/', $_SERVER['HTTP_IF'], $match)) {
|
&& preg_match('/\(<([^>]*)>\s+\["([^""]+)"\]\)/', $_SERVER['HTTP_IF'], $match)) {
|
||||||
$token = $match[1];
|
$token = $match[1];
|
||||||
$request_etag = $match[2];
|
$request_etag = $match[2];
|
||||||
$etag = current($this->storage->propfind($uri, ['DAV::getetag'], 0));
|
$etag = current($this->storage->properties($uri, ['DAV::getetag'], 0));
|
||||||
|
|
||||||
if ($request_etag != $etag) {
|
if ($request_etag != $etag) {
|
||||||
throw new Exception('Resource is locked and etag does not match', 412);
|
throw new Exception('Resource is locked and etag does not match', 412);
|
||||||
@ -1258,10 +1115,9 @@ namespace KD2\WebDAV
|
|||||||
{
|
{
|
||||||
$uri = parse_url($source, PHP_URL_PATH);
|
$uri = parse_url($source, PHP_URL_PATH);
|
||||||
$uri = rawurldecode($uri);
|
$uri = rawurldecode($uri);
|
||||||
$uri = trim($uri, '/');
|
$uri = rtrim($uri, '/');
|
||||||
$uri = '/' . $uri;
|
|
||||||
|
|
||||||
if ($uri . '/' === $this->base_uri) {
|
if ($uri . '/' == $this->base_uri) {
|
||||||
$uri .= '/';
|
$uri .= '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1286,7 +1142,6 @@ namespace KD2\WebDAV
|
|||||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = '/' . ltrim($uri, '/');
|
|
||||||
$this->original_uri = $uri;
|
$this->original_uri = $uri;
|
||||||
|
|
||||||
if ($uri . '/' == $this->base_uri) {
|
if ($uri . '/' == $this->base_uri) {
|
||||||
@ -1297,7 +1152,7 @@ namespace KD2\WebDAV
|
|||||||
$uri = substr($uri, strlen($this->base_uri));
|
$uri = substr($uri, strlen($this->base_uri));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$this->log('<= %s is not a managed URL (%s)', $uri, $this->base_uri);
|
$this->log('<= %s is not a managed URL', $uri);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1390,14 +1245,14 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
abstract public function exists(string $uri): bool;
|
abstract public function exists(string $uri): bool;
|
||||||
|
|
||||||
abstract public function propfind(string $uri, ?array $requested_properties, int $depth): ?array;
|
abstract public function properties(string $uri, ?array $requested_properties, int $depth): ?array;
|
||||||
|
|
||||||
public function proppatch(string $uri, array $properties): array
|
public function setProperties(string $uri, string $body): void
|
||||||
{
|
{
|
||||||
// By default, properties are not saved
|
// By default, properties are not saved
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function put(string $uri, $pointer, ?string $hash_algo, ?string $hash): bool;
|
abstract public function put(string $uri, $pointer, ?string $hash, ?int $mtime): bool;
|
||||||
|
|
||||||
abstract public function delete(string $uri): void;
|
abstract public function delete(string $uri): void;
|
||||||
|
|
||||||
@ -1409,8 +1264,6 @@ namespace KD2\WebDAV
|
|||||||
|
|
||||||
abstract public function list(string $uri, array $properties): iterable;
|
abstract public function list(string $uri, array $properties): iterable;
|
||||||
|
|
||||||
abstract public function touch(string $uri, \DateTimeInterface $timestamp): bool;
|
|
||||||
|
|
||||||
public function lock(string $uri, string $token, string $scope): void
|
public function lock(string $uri, string $token, string $scope): void
|
||||||
{
|
{
|
||||||
// By default locking is not implemented
|
// By default locking is not implemented
|
||||||
@ -1448,9 +1301,9 @@ namespace PicoDAV
|
|||||||
|
|
||||||
public array $users = [];
|
public array $users = [];
|
||||||
|
|
||||||
public function __construct(string $path)
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->path = $path . '/';
|
$this->path = __DIR__ . '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function auth(): bool
|
public function auth(): bool
|
||||||
@ -1466,15 +1319,13 @@ namespace PicoDAV
|
|||||||
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
|
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
|
||||||
$password = $_SERVER['PHP_AUTH_PW'] ?? null;
|
$password = $_SERVER['PHP_AUTH_PW'] ?? null;
|
||||||
|
|
||||||
if (!array_key_exists($user, $this->users)) {
|
$hash = $this->users[$user]['password'] ?? null;
|
||||||
|
|
||||||
|
if (!$hash) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hash = $this->users[$user]['password'] ?? null;
|
if (!password_verify($password, $hash)) {
|
||||||
|
|
||||||
// If no password is set, we accept any password as we consider that a .htaccess/.htpasswd
|
|
||||||
// access has been granted
|
|
||||||
if (null !== $hash && !password_verify($password, $hash)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1622,8 +1473,6 @@ namespace PicoDAV
|
|||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
|
|
||||||
switch ($name) {
|
switch ($name) {
|
||||||
case 'DAV::displayname':
|
|
||||||
return basename($uri);
|
|
||||||
case 'DAV::getcontentlength':
|
case 'DAV::getcontentlength':
|
||||||
return is_dir($target) ? null : filesize($target);
|
return is_dir($target) ? null : filesize($target);
|
||||||
case 'DAV::getcontenttype':
|
case 'DAV::getcontenttype':
|
||||||
@ -1633,7 +1482,12 @@ namespace PicoDAV
|
|||||||
case 'DAV::resourcetype':
|
case 'DAV::resourcetype':
|
||||||
return is_dir($target) ? 'collection' : '';
|
return is_dir($target) ? 'collection' : '';
|
||||||
case 'DAV::getlastmodified':
|
case 'DAV::getlastmodified':
|
||||||
|
if (!$uri && $depth == 0 && is_dir($target)) {
|
||||||
|
$mtime = self::getDirectoryMTime($target);
|
||||||
|
}
|
||||||
|
else {
|
||||||
$mtime = filemtime($target);
|
$mtime = filemtime($target);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$mtime) {
|
if (!$mtime) {
|
||||||
return null;
|
return null;
|
||||||
@ -1669,7 +1523,7 @@ namespace PicoDAV
|
|||||||
|
|
||||||
return $permissions;
|
return $permissions;
|
||||||
case Server::PROP_DIGEST_MD5:
|
case Server::PROP_DIGEST_MD5:
|
||||||
if (!is_file($target) || is_dir($target) || !is_readable($target)) {
|
if (!is_file($target)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1681,7 +1535,7 @@ namespace PicoDAV
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function propfind(string $uri, ?array $properties, int $depth): ?array
|
public function properties(string $uri, ?array $properties, int $depth): ?array
|
||||||
{
|
{
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
|
|
||||||
@ -1690,7 +1544,7 @@ namespace PicoDAV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null === $properties) {
|
if (null === $properties) {
|
||||||
$properties = Server::BASIC_PROPERTIES;
|
$properties = WebDAV::BASIC_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
$out = [];
|
$out = [];
|
||||||
@ -1706,7 +1560,7 @@ namespace PicoDAV
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function put(string $uri, $pointer, ?string $hash_algo, ?string $hash): bool
|
public function put(string $uri, $pointer, ?string $hash, ?int $mtime): bool
|
||||||
{
|
{
|
||||||
if (preg_match(self::PUT_IGNORE_PATTERN, basename($uri))) {
|
if (preg_match(self::PUT_IGNORE_PATTERN, basename($uri))) {
|
||||||
return false;
|
return false;
|
||||||
@ -1732,7 +1586,7 @@ namespace PicoDAV
|
|||||||
$size = 0;
|
$size = 0;
|
||||||
$quota = disk_free_space($this->path);
|
$quota = disk_free_space($this->path);
|
||||||
|
|
||||||
$tmp_file = $this->path . '.tmp.' . sha1($target);
|
$tmp_file = '.tmp.' . sha1($target);
|
||||||
$out = fopen($tmp_file, 'w');
|
$out = fopen($tmp_file, 'w');
|
||||||
|
|
||||||
while (!feof($pointer)) {
|
while (!feof($pointer)) {
|
||||||
@ -1752,20 +1606,20 @@ namespace PicoDAV
|
|||||||
|
|
||||||
if ($delete) {
|
if ($delete) {
|
||||||
@unlink($tmp_file);
|
@unlink($tmp_file);
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 507);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
elseif ($hash && $hash_algo == 'MD5' && md5_file($tmp_file) != $hash) {
|
elseif ($hash && md5_file($tmp_file) != $hash) {
|
||||||
@unlink($tmp_file);
|
@unlink($tmp_file);
|
||||||
throw new WebDAV_Exception('The data sent does not match the supplied MD5 hash', 400);
|
throw new WebDAV_Exception('The data sent does not match the supplied MD5 hash', 400);
|
||||||
}
|
}
|
||||||
elseif ($hash && $hash_algo == 'SHA1' && sha1_file($tmp_file) != $hash) {
|
|
||||||
@unlink($tmp_file);
|
|
||||||
throw new WebDAV_Exception('The data sent does not match the supplied SHA1 hash', 400);
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
rename($tmp_file, $target);
|
rename($tmp_file, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($mtime) {
|
||||||
|
@touch($target, $mtime);
|
||||||
|
}
|
||||||
|
|
||||||
return $new;
|
return $new;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1827,7 +1681,7 @@ namespace PicoDAV
|
|||||||
$quota = disk_free_space($this->path);
|
$quota = disk_free_space($this->path);
|
||||||
|
|
||||||
if (filesize($source) > $quota) {
|
if (filesize($source) > $quota) {
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 507);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1875,7 +1729,7 @@ namespace PicoDAV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!disk_free_space($this->path)) {
|
if (!disk_free_space($this->path)) {
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 507);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
@ -1892,10 +1746,28 @@ namespace PicoDAV
|
|||||||
mkdir($target, 0770);
|
mkdir($target, 0770);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function touch(string $uri, \DateTimeInterface $datetime): bool
|
static public function getDirectoryMTime(string $path): int
|
||||||
{
|
{
|
||||||
$target = $this->path . $uri;
|
$last = 0;
|
||||||
return @touch($target, $datetime->getTimestamp());
|
$path = rtrim($path, '/');
|
||||||
|
|
||||||
|
foreach (self::glob($path, '/*', GLOB_NOSORT) as $f) {
|
||||||
|
if (is_dir($f)) {
|
||||||
|
$m = self::getDirectoryMTime($f);
|
||||||
|
|
||||||
|
if ($m > $last) {
|
||||||
|
$last = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$m = filemtime($f);
|
||||||
|
|
||||||
|
if ($m > $last) {
|
||||||
|
$last = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1937,26 +1809,6 @@ namespace PicoDAV
|
|||||||
|
|
||||||
parent::error($e);
|
parent::error($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string $_log = '';
|
|
||||||
|
|
||||||
public function log(string $message, ...$params): void
|
|
||||||
{
|
|
||||||
if (!HTTP_LOG_FILE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->_log .= vsprintf($message, $params) . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if (!$this->_log) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(HTTP_LOG_FILE, $this->_log, \FILE_APPEND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1965,10 +1817,7 @@ namespace {
|
|||||||
use PicoDAV\Storage;
|
use PicoDAV\Storage;
|
||||||
|
|
||||||
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
$self = $_SERVER['SCRIPT_FILENAME'];
|
$root = substr(__DIR__, strlen($_SERVER['DOCUMENT_ROOT']));
|
||||||
$self_dir = dirname($self);
|
|
||||||
$root = substr(dirname($_SERVER['SCRIPT_FILENAME']), strlen($_SERVER['DOCUMENT_ROOT']));
|
|
||||||
$root = '/' . ltrim($root, '/');
|
|
||||||
|
|
||||||
if (false !== strpos($uri, '..')) {
|
if (false !== strpos($uri, '..')) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
@ -1977,8 +1826,8 @@ namespace {
|
|||||||
|
|
||||||
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
||||||
|
|
||||||
if (!empty($_SERVER['SERVER_SOFTWARE']) && stristr($_SERVER['SERVER_SOFTWARE'], 'apache') && !file_exists($self_dir . '/.htaccess')) {
|
if (!empty($_SERVER['SERVER_SOFTWARE']) && stristr($_SERVER['SERVER_SOFTWARE'], 'apache') && !file_exists(__DIR__ . '/.htaccess')) {
|
||||||
file_put_contents($self_dir . '/.htaccess', str_replace('index.php', basename($self), 'DirectoryIndex disabled
|
file_put_contents(__DIR__ . '/.htaccess', 'DirectoryIndex disabled
|
||||||
|
|
||||||
RedirectMatch 404 \\.picodav\\.ini
|
RedirectMatch 404 \\.picodav\\.ini
|
||||||
|
|
||||||
@ -1992,7 +1841,7 @@ RewriteBase /
|
|||||||
#RewriteCond %{REQUEST_METHOD} !GET
|
#RewriteCond %{REQUEST_METHOD} !GET
|
||||||
|
|
||||||
RewriteRule ^.*$ /index.php [END]
|
RewriteRule ^.*$ /index.php [END]
|
||||||
'));
|
');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($relative_uri == '.webdav/webdav.js' || $relative_uri == '.webdav/webdav.css') {
|
if ($relative_uri == '.webdav/webdav.js' || $relative_uri == '.webdav/webdav.css') {
|
||||||
@ -2014,11 +1863,11 @@ RewriteRule ^.*$ /index.php [END]
|
|||||||
$fp = fopen(__FILE__, 'r');
|
$fp = fopen(__FILE__, 'r');
|
||||||
|
|
||||||
if ($relative_uri == '.webdav/webdav.js') {
|
if ($relative_uri == '.webdav/webdav.js') {
|
||||||
fseek($fp, 55024, SEEK_SET);
|
fseek($fp, 50022, SEEK_SET);
|
||||||
echo fread($fp, 27891);
|
echo fread($fp, 27769);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fseek($fp, 55024 + 27891, SEEK_SET);
|
fseek($fp, 50022 + 27769, SEEK_SET);
|
||||||
echo fread($fp, 7004);
|
echo fread($fp, 7004);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2027,20 +1876,20 @@ RewriteRule ^.*$ /index.php [END]
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config_file = $self_dir . '/.picodav.ini';
|
const CONFIG_FILE = __DIR__ . '/.picodav.ini';
|
||||||
define('PicoDAV\INTERNAL_FILES', ['.picodav.ini', $self_dir, '.webdav/webdav.js', '.webdav/webdav.css']);
|
define('PicoDAV\INTERNAL_FILES', ['.picodav.ini', basename(__FILE__), '.webdav/webdav.js', '.webdav/webdav.css']);
|
||||||
|
|
||||||
const DEFAULT_CONFIG = [
|
const DEFAULT_CONFIG = [
|
||||||
'ANONYMOUS_READ' => true,
|
'ANONYMOUS_READ' => true,
|
||||||
'ANONYMOUS_WRITE' => false,
|
'ANONYMOUS_WRITE' => false,
|
||||||
'HTTP_LOG_FILE' => null,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$config = [];
|
$config = [];
|
||||||
$storage = new Storage($self_dir);
|
$storage = new Storage;
|
||||||
|
|
||||||
if (file_exists($config_file)) {
|
|
||||||
$config = parse_ini_file($config_file, true);
|
if (file_exists(CONFIG_FILE)) {
|
||||||
|
$config = parse_ini_file(CONFIG_FILE, true);
|
||||||
$users = array_filter($config, 'is_array');
|
$users = array_filter($config, 'is_array');
|
||||||
$config = array_diff_key($config, $users);
|
$config = array_diff_key($config, $users);
|
||||||
$config = array_change_key_case($config, \CASE_UPPER);
|
$config = array_change_key_case($config, \CASE_UPPER);
|
||||||
@ -2054,7 +1903,7 @@ RewriteRule ^.*$ /index.php [END]
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count($replace)) {
|
if (count($replace)) {
|
||||||
$lines = file($config_file);
|
$lines = file(CONFIG_FILE);
|
||||||
$current = null;
|
$current = null;
|
||||||
|
|
||||||
foreach ($lines as &$line) {
|
foreach ($lines as &$line) {
|
||||||
@ -2070,7 +1919,7 @@ RewriteRule ^.*$ /index.php [END]
|
|||||||
|
|
||||||
unset($line, $current);
|
unset($line, $current);
|
||||||
|
|
||||||
file_put_contents($config_file, implode('', $lines));
|
file_put_contents(CONFIG_FILE, implode('', $lines));
|
||||||
}
|
}
|
||||||
|
|
||||||
$storage->users = $users;
|
$storage->users = $users;
|
||||||
@ -2089,14 +1938,14 @@ RewriteRule ^.*$ /index.php [END]
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$dav = new Server();
|
$dav = new Server;
|
||||||
$dav->setStorage($storage);
|
$dav->setStorage($storage);
|
||||||
|
|
||||||
$dav->setBaseURI($root);
|
$dav->setBaseURI($root);
|
||||||
|
|
||||||
if (!$dav->route($uri)) {
|
if (!$dav->route($uri)) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
die('Unknown URL, sorry.');
|
die('Invalid URL, sorry');
|
||||||
}
|
}
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
@ -2109,7 +1958,7 @@ const WebDAVNavigator = (url, options) => {
|
|||||||
// https://github.com/commit-intl/micro-down
|
// https://github.com/commit-intl/micro-down
|
||||||
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+"</"+n+">"}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"<li>"+g(e+"\n"+(t=r||"").replace(new RegExp("^"+(t.match(/^\s+/)||"")[0],"gm"),"").replace(o,c))+"</li>";var t})+"</"+e+">":""}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,[/<!--((.|\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,'<a name="$1"></a>',/^(#+) +(.*)(?:$)/gm,function(n,e,r){return l("h"+e.length,g(r))},/^(===+|---+)(?=\s*$)/gm,"<hr>"],p,u)}var i=this,a=function(n){return n?n.replace(/"/g,""").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<e&&(r=l("strong",r)),e%2&&(r=l("em",r)),r},/(~{1,3})((.|\n)+?)\1/g,function(n,e,r){return l([,"u","s","del"][e.length],t(r))},/ \n|\n /g,"<br>"],t)},f=function(n,e,r,t){for(var u,c=0;c<e.length;){if(u=e[c++].exec(n))return r(n.slice(0,u.index),t)+("string"==typeof e[c]?e[c].replace(/\$(\d)/g,function(n,e){return u[e]}):e[c].apply(i,u))+r(n.slice(u.index+u[0].length),t);c++}return n},p=function(n,e){n=n.replace(/[\r\v\b\f]/g,"").replace(/\\./g,function(n){return"&#"+n.charCodeAt(1)+";"});var r=t(n,e);return r!==n||r.match(/^[\s\n]*$/i)||(r=g(r).replace(/((.|\n)+?)(\n\n+|$)/g,function(n,e){return l("p",e)})),r.replace(/&#(\d+);/g,function(n,e){return String.fromCharCode(parseInt(e))})};return{parse:p,block:t,inline:r,inlineBlock:g}}();
|
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+"</"+n+">"}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"<li>"+g(e+"\n"+(t=r||"").replace(new RegExp("^"+(t.match(/^\s+/)||"")[0],"gm"),"").replace(o,c))+"</li>";var t})+"</"+e+">":""}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,[/<!--((.|\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,'<a name="$1"></a>',/^(#+) +(.*)(?:$)/gm,function(n,e,r){return l("h"+e.length,g(r))},/^(===+|---+)(?=\s*$)/gm,"<hr>"],p,u)}var i=this,a=function(n){return n?n.replace(/"/g,""").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<e&&(r=l("strong",r)),e%2&&(r=l("em",r)),r},/(~{1,3})((.|\n)+?)\1/g,function(n,e,r){return l([,"u","s","del"][e.length],t(r))},/ \n|\n /g,"<br>"],t)},f=function(n,e,r,t){for(var u,c=0;c<e.length;){if(u=e[c++].exec(n))return r(n.slice(0,u.index),t)+("string"==typeof e[c]?e[c].replace(/\$(\d)/g,function(n,e){return u[e]}):e[c].apply(i,u))+r(n.slice(u.index+u[0].length),t);c++}return n},p=function(n,e){n=n.replace(/[\r\v\b\f]/g,"").replace(/\\./g,function(n){return"&#"+n.charCodeAt(1)+";"});var r=t(n,e);return r!==n||r.match(/^[\s\n]*$/i)||(r=g(r).replace(/((.|\n)+?)(\n\n+|$)/g,function(n,e){return l("p",e)})),r.replace(/&#(\d+);/g,function(n,e){return String.fromCharCode(parseInt(e))})};return{parse:p,block:t,inline:r,inlineBlock:g}}();
|
||||||
|
|
||||||
const PREVIEW_TYPES = /^image\/(png|webp|svg|jpeg|jpg|gif|png)|^application\/pdf|^text\/|^audio\/|^video\/|application\/x-empty/;
|
const PREVIEW_TYPES = /^image\/(png|webp|svg|jpeg|jpg|gif|png)|^application\/pdf|^text\/|^audio\/|^video\//;
|
||||||
|
|
||||||
const _ = key => typeof lang_strings != 'undefined' && key in lang_strings ? lang_strings[key] : key;
|
const _ = key => typeof lang_strings != 'undefined' && key in lang_strings ? lang_strings[key] : key;
|
||||||
|
|
||||||
@ -2154,14 +2003,14 @@ const WebDAVNavigator = (url, options) => {
|
|||||||
const dir_row_tpl = `<tr data-permissions="%permissions%"><td class="thumb"><span class="icon dir"><b>%icon%</b></span></td><th colspan="2"><a href="%uri%">%name%</a></th><td>%modified%</td><td class="buttons"><div></div></td></tr>`;
|
const dir_row_tpl = `<tr data-permissions="%permissions%"><td class="thumb"><span class="icon dir"><b>%icon%</b></span></td><th colspan="2"><a href="%uri%">%name%</a></th><td>%modified%</td><td class="buttons"><div></div></td></tr>`;
|
||||||
const file_row_tpl = `<tr data-permissions="%permissions%" data-mime="%mime%" data-size="%size%"><td class="thumb"><span class="icon %icon%"><b>%icon%</b></span></td><th><a href="%uri%">%name%</a></th><td class="size">%size_bytes%</td><td>%modified%</td><td class="buttons"><div><a href="%uri%" download class="btn">${_('Download')}</a></div></td></tr>`;
|
const file_row_tpl = `<tr data-permissions="%permissions%" data-mime="%mime%" data-size="%size%"><td class="thumb"><span class="icon %icon%"><b>%icon%</b></span></td><th><a href="%uri%">%name%</a></th><td class="size">%size_bytes%</td><td>%modified%</td><td class="buttons"><div><a href="%uri%" download class="btn">${_('Download')}</a></div></td></tr>`;
|
||||||
|
|
||||||
const propfind_tpl = '<'+ `?xml version="1.0" encoding="UTF-8"?>
|
const propfind_tpl = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<D:propfind xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">
|
<D:propfind xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||||
<D:prop>
|
<D:prop>
|
||||||
<D:getlastmodified/><D:getcontenttype/><D:getcontentlength/><D:resourcetype/><D:displayname/><oc:permissions/>
|
<D:getlastmodified/><D:getcontenttype/><D:getcontentlength/><D:resourcetype/><D:displayname/><oc:permissions/>
|
||||||
</D:prop>
|
</D:prop>
|
||||||
</D:propfind>`;
|
</D:propfind>`;
|
||||||
|
|
||||||
const wopi_propfind_tpl = '<' + `?xml version="1.0" encoding="UTF-8"?>
|
const wopi_propfind_tpl = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<D:propfind xmlns:D="DAV:" xmlns:W="https://interoperability.blob.core.windows.net/files/MS-WOPI/">
|
<D:propfind xmlns:D="DAV:" xmlns:W="https://interoperability.blob.core.windows.net/files/MS-WOPI/">
|
||||||
<D:prop>
|
<D:prop>
|
||||||
<W:file-url/><W:token/><W:token-ttl/>
|
<W:file-url/><W:token/><W:token-ttl/>
|
||||||
@ -2261,14 +2110,12 @@ const WebDAVNavigator = (url, options) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wopi_init = async () => {
|
const wopi_init = async () => {
|
||||||
try {
|
if (!wopi_discovery_url) {
|
||||||
var d = await reqXML('GET', wopi_discovery_url);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
reloadListing();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var d = await reqXML('GET', wopi_discovery_url);
|
||||||
|
|
||||||
d.querySelectorAll('app').forEach(app => {
|
d.querySelectorAll('app').forEach(app => {
|
||||||
var mime = (a = app.getAttribute('name').match(/^.*\/.*$/)) ? a[0] : null;
|
var mime = (a = app.getAttribute('name').match(/^.*\/.*$/)) ? a[0] : null;
|
||||||
wopi_mimes[mime] = {};
|
wopi_mimes[mime] = {};
|
||||||
@ -2457,10 +2304,6 @@ const WebDAVNavigator = (url, options) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (date) => {
|
const formatDate = (date) => {
|
||||||
if (isNaN(date)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = new Date;
|
var now = new Date;
|
||||||
var nb_hours = (+(now) - +(date)) / 3600 / 1000;
|
var nb_hours = (+(now) - +(date)) / 3600 / 1000;
|
||||||
|
|
||||||
@ -2651,7 +2494,7 @@ const WebDAVNavigator = (url, options) => {
|
|||||||
$('.download_all').onclick = download_all;
|
$('.download_all').onclick = download_all;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root_permissions || root_permissions.indexOf('C') != -1 || root_permissions.indexOf('K') != -1) {
|
if (!root_permissions || root_permissions.indexOf('CK') != -1) {
|
||||||
$('.upload').insertAdjacentHTML('afterbegin', create_buttons);
|
$('.upload').insertAdjacentHTML('afterbegin', create_buttons);
|
||||||
|
|
||||||
$('.mkdir').onclick = () => {
|
$('.mkdir').onclick = () => {
|
||||||
@ -2886,10 +2729,11 @@ const WebDAVNavigator = (url, options) => {
|
|||||||
|
|
||||||
document.querySelector('html').innerHTML = html_tpl;
|
document.querySelector('html').innerHTML = html_tpl;
|
||||||
|
|
||||||
// Wait for WOPI discovery before creating the list
|
|
||||||
if (wopi_discovery_url) {
|
if (wopi_discovery_url) {
|
||||||
|
// Wait for WOPI discovery before creating the list
|
||||||
wopi_init();
|
wopi_init();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
reloadListing();
|
reloadListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
server.php
120
server.php
@ -25,9 +25,9 @@ namespace PicoDAV
|
|||||||
|
|
||||||
public array $users = [];
|
public array $users = [];
|
||||||
|
|
||||||
public function __construct(string $path)
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->path = $path . '/';
|
$this->path = __DIR__ . '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function auth(): bool
|
public function auth(): bool
|
||||||
@ -43,15 +43,13 @@ namespace PicoDAV
|
|||||||
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
|
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
|
||||||
$password = $_SERVER['PHP_AUTH_PW'] ?? null;
|
$password = $_SERVER['PHP_AUTH_PW'] ?? null;
|
||||||
|
|
||||||
if (!array_key_exists($user, $this->users)) {
|
$hash = $this->users[$user]['password'] ?? null;
|
||||||
|
|
||||||
|
if (!$hash) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hash = $this->users[$user]['password'] ?? null;
|
if (!password_verify($password, $hash)) {
|
||||||
|
|
||||||
// If no password is set, we accept any password as we consider that a .htaccess/.htpasswd
|
|
||||||
// access has been granted
|
|
||||||
if (null !== $hash && !password_verify($password, $hash)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,8 +197,6 @@ namespace PicoDAV
|
|||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
|
|
||||||
switch ($name) {
|
switch ($name) {
|
||||||
case 'DAV::displayname':
|
|
||||||
return basename($uri);
|
|
||||||
case 'DAV::getcontentlength':
|
case 'DAV::getcontentlength':
|
||||||
return is_dir($target) ? null : filesize($target);
|
return is_dir($target) ? null : filesize($target);
|
||||||
case 'DAV::getcontenttype':
|
case 'DAV::getcontenttype':
|
||||||
@ -210,7 +206,12 @@ namespace PicoDAV
|
|||||||
case 'DAV::resourcetype':
|
case 'DAV::resourcetype':
|
||||||
return is_dir($target) ? 'collection' : '';
|
return is_dir($target) ? 'collection' : '';
|
||||||
case 'DAV::getlastmodified':
|
case 'DAV::getlastmodified':
|
||||||
|
if (!$uri && $depth == 0 && is_dir($target)) {
|
||||||
|
$mtime = self::getDirectoryMTime($target);
|
||||||
|
}
|
||||||
|
else {
|
||||||
$mtime = filemtime($target);
|
$mtime = filemtime($target);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$mtime) {
|
if (!$mtime) {
|
||||||
return null;
|
return null;
|
||||||
@ -246,7 +247,7 @@ namespace PicoDAV
|
|||||||
|
|
||||||
return $permissions;
|
return $permissions;
|
||||||
case Server::PROP_DIGEST_MD5:
|
case Server::PROP_DIGEST_MD5:
|
||||||
if (!is_file($target) || is_dir($target) || !is_readable($target)) {
|
if (!is_file($target)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +259,7 @@ namespace PicoDAV
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function propfind(string $uri, ?array $properties, int $depth): ?array
|
public function properties(string $uri, ?array $properties, int $depth): ?array
|
||||||
{
|
{
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
|
|
||||||
@ -267,7 +268,7 @@ namespace PicoDAV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null === $properties) {
|
if (null === $properties) {
|
||||||
$properties = Server::BASIC_PROPERTIES;
|
$properties = WebDAV::BASIC_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
$out = [];
|
$out = [];
|
||||||
@ -283,7 +284,7 @@ namespace PicoDAV
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function put(string $uri, $pointer, ?string $hash_algo, ?string $hash): bool
|
public function put(string $uri, $pointer, ?string $hash, ?int $mtime): bool
|
||||||
{
|
{
|
||||||
if (preg_match(self::PUT_IGNORE_PATTERN, basename($uri))) {
|
if (preg_match(self::PUT_IGNORE_PATTERN, basename($uri))) {
|
||||||
return false;
|
return false;
|
||||||
@ -309,7 +310,7 @@ namespace PicoDAV
|
|||||||
$size = 0;
|
$size = 0;
|
||||||
$quota = disk_free_space($this->path);
|
$quota = disk_free_space($this->path);
|
||||||
|
|
||||||
$tmp_file = $this->path . '.tmp.' . sha1($target);
|
$tmp_file = '.tmp.' . sha1($target);
|
||||||
$out = fopen($tmp_file, 'w');
|
$out = fopen($tmp_file, 'w');
|
||||||
|
|
||||||
while (!feof($pointer)) {
|
while (!feof($pointer)) {
|
||||||
@ -329,20 +330,20 @@ namespace PicoDAV
|
|||||||
|
|
||||||
if ($delete) {
|
if ($delete) {
|
||||||
@unlink($tmp_file);
|
@unlink($tmp_file);
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 507);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
elseif ($hash && $hash_algo == 'MD5' && md5_file($tmp_file) != $hash) {
|
elseif ($hash && md5_file($tmp_file) != $hash) {
|
||||||
@unlink($tmp_file);
|
@unlink($tmp_file);
|
||||||
throw new WebDAV_Exception('The data sent does not match the supplied MD5 hash', 400);
|
throw new WebDAV_Exception('The data sent does not match the supplied MD5 hash', 400);
|
||||||
}
|
}
|
||||||
elseif ($hash && $hash_algo == 'SHA1' && sha1_file($tmp_file) != $hash) {
|
|
||||||
@unlink($tmp_file);
|
|
||||||
throw new WebDAV_Exception('The data sent does not match the supplied SHA1 hash', 400);
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
rename($tmp_file, $target);
|
rename($tmp_file, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($mtime) {
|
||||||
|
@touch($target, $mtime);
|
||||||
|
}
|
||||||
|
|
||||||
return $new;
|
return $new;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +405,7 @@ namespace PicoDAV
|
|||||||
$quota = disk_free_space($this->path);
|
$quota = disk_free_space($this->path);
|
||||||
|
|
||||||
if (filesize($source) > $quota) {
|
if (filesize($source) > $quota) {
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 507);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,7 +453,7 @@ namespace PicoDAV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!disk_free_space($this->path)) {
|
if (!disk_free_space($this->path)) {
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 507);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
@ -469,10 +470,28 @@ namespace PicoDAV
|
|||||||
mkdir($target, 0770);
|
mkdir($target, 0770);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function touch(string $uri, \DateTimeInterface $datetime): bool
|
static public function getDirectoryMTime(string $path): int
|
||||||
{
|
{
|
||||||
$target = $this->path . $uri;
|
$last = 0;
|
||||||
return @touch($target, $datetime->getTimestamp());
|
$path = rtrim($path, '/');
|
||||||
|
|
||||||
|
foreach (self::glob($path, '/*', GLOB_NOSORT) as $f) {
|
||||||
|
if (is_dir($f)) {
|
||||||
|
$m = self::getDirectoryMTime($f);
|
||||||
|
|
||||||
|
if ($m > $last) {
|
||||||
|
$last = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$m = filemtime($f);
|
||||||
|
|
||||||
|
if ($m > $last) {
|
||||||
|
$last = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,26 +533,6 @@ namespace PicoDAV
|
|||||||
|
|
||||||
parent::error($e);
|
parent::error($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string $_log = '';
|
|
||||||
|
|
||||||
public function log(string $message, ...$params): void
|
|
||||||
{
|
|
||||||
if (!HTTP_LOG_FILE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->_log .= vsprintf($message, $params) . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if (!$this->_log) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(HTTP_LOG_FILE, $this->_log, \FILE_APPEND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,10 +541,7 @@ namespace {
|
|||||||
use PicoDAV\Storage;
|
use PicoDAV\Storage;
|
||||||
|
|
||||||
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
$self = $_SERVER['SCRIPT_FILENAME'];
|
$root = substr(__DIR__, strlen($_SERVER['DOCUMENT_ROOT']));
|
||||||
$self_dir = dirname($self);
|
|
||||||
$root = substr(dirname($_SERVER['SCRIPT_FILENAME']), strlen($_SERVER['DOCUMENT_ROOT']));
|
|
||||||
$root = '/' . ltrim($root, '/');
|
|
||||||
|
|
||||||
if (false !== strpos($uri, '..')) {
|
if (false !== strpos($uri, '..')) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
@ -554,8 +550,8 @@ namespace {
|
|||||||
|
|
||||||
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
||||||
|
|
||||||
if (!empty($_SERVER['SERVER_SOFTWARE']) && stristr($_SERVER['SERVER_SOFTWARE'], 'apache') && !file_exists($self_dir . '/.htaccess')) {
|
if (!empty($_SERVER['SERVER_SOFTWARE']) && stristr($_SERVER['SERVER_SOFTWARE'], 'apache') && !file_exists(__DIR__ . '/.htaccess')) {
|
||||||
file_put_contents($self_dir . '/.htaccess', str_replace('index.php', basename($self), /*__HTACCESS__*/));
|
file_put_contents(__DIR__ . '/.htaccess', /*__HTACCESS__*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($relative_uri == '.webdav/webdav.js' || $relative_uri == '.webdav/webdav.css') {
|
if ($relative_uri == '.webdav/webdav.js' || $relative_uri == '.webdav/webdav.css') {
|
||||||
@ -590,20 +586,20 @@ namespace {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config_file = $self_dir . '/.picodav.ini';
|
const CONFIG_FILE = __DIR__ . '/.picodav.ini';
|
||||||
define('PicoDAV\INTERNAL_FILES', ['.picodav.ini', $self_dir, '.webdav/webdav.js', '.webdav/webdav.css']);
|
define('PicoDAV\INTERNAL_FILES', ['.picodav.ini', basename(__FILE__), '.webdav/webdav.js', '.webdav/webdav.css']);
|
||||||
|
|
||||||
const DEFAULT_CONFIG = [
|
const DEFAULT_CONFIG = [
|
||||||
'ANONYMOUS_READ' => true,
|
'ANONYMOUS_READ' => true,
|
||||||
'ANONYMOUS_WRITE' => false,
|
'ANONYMOUS_WRITE' => false,
|
||||||
'HTTP_LOG_FILE' => null,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$config = [];
|
$config = [];
|
||||||
$storage = new Storage($self_dir);
|
$storage = new Storage;
|
||||||
|
|
||||||
if (file_exists($config_file)) {
|
|
||||||
$config = parse_ini_file($config_file, true);
|
if (file_exists(CONFIG_FILE)) {
|
||||||
|
$config = parse_ini_file(CONFIG_FILE, true);
|
||||||
$users = array_filter($config, 'is_array');
|
$users = array_filter($config, 'is_array');
|
||||||
$config = array_diff_key($config, $users);
|
$config = array_diff_key($config, $users);
|
||||||
$config = array_change_key_case($config, \CASE_UPPER);
|
$config = array_change_key_case($config, \CASE_UPPER);
|
||||||
@ -617,7 +613,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count($replace)) {
|
if (count($replace)) {
|
||||||
$lines = file($config_file);
|
$lines = file(CONFIG_FILE);
|
||||||
$current = null;
|
$current = null;
|
||||||
|
|
||||||
foreach ($lines as &$line) {
|
foreach ($lines as &$line) {
|
||||||
@ -633,7 +629,7 @@ namespace {
|
|||||||
|
|
||||||
unset($line, $current);
|
unset($line, $current);
|
||||||
|
|
||||||
file_put_contents($config_file, implode('', $lines));
|
file_put_contents(CONFIG_FILE, implode('', $lines));
|
||||||
}
|
}
|
||||||
|
|
||||||
$storage->users = $users;
|
$storage->users = $users;
|
||||||
@ -652,14 +648,14 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$dav = new Server();
|
$dav = new Server;
|
||||||
$dav->setStorage($storage);
|
$dav->setStorage($storage);
|
||||||
|
|
||||||
$dav->setBaseURI($root);
|
$dav->setBaseURI($root);
|
||||||
|
|
||||||
if (!$dav->route($uri)) {
|
if (!$dav->route($uri)) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
die('Unknown URL, sorry.');
|
die('Invalid URL, sorry');
|
||||||
}
|
}
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
|
Loading…
Reference in New Issue
Block a user