Allow to restrict to read-only access
This commit is contained in:
parent
5aef69423a
commit
be2f8415c6
3 changed files with 320 additions and 147 deletions
|
@ -1,2 +1,5 @@
|
||||||
# nanokaradav
|
# Nano-KaraDAV
|
||||||
Single-file WebDAV server, just drop it in a directory!
|
|
||||||
|
## Single-file WebDAV server, just drop it in a directory!
|
||||||
|
|
||||||
|
If you drop the `index.php` file in a directory, and
|
377
index.php
377
index.php
|
@ -1283,7 +1283,7 @@ namespace KD2\WebDAV
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace NanoKaraDAV
|
namespace PicoDAV
|
||||||
{
|
{
|
||||||
use KD2\WebDAV\AbstractStorage;
|
use KD2\WebDAV\AbstractStorage;
|
||||||
use KD2\WebDAV\Exception as WebDAV_Exception;
|
use KD2\WebDAV\Exception as WebDAV_Exception;
|
||||||
|
@ -1320,7 +1320,7 @@ namespace NanoKaraDAV
|
||||||
$files = array_diff($files, $dirs);
|
$files = array_diff($files, $dirs);
|
||||||
|
|
||||||
// Remove PHP files from listings
|
// Remove PHP files from listings
|
||||||
$files = array_filter($files, fn($a) => !preg_match('/\.(?:php\d?|phtml|phps)$/i', $a));
|
$files = array_filter($files, fn($a) => !preg_match('/\.(?:php\d?|phtml|phps)$|^\./i', $a));
|
||||||
|
|
||||||
if (!$uri) {
|
if (!$uri) {
|
||||||
$files = array_diff($files, ['webdav.js', 'webdav.css']);
|
$files = array_diff($files, ['webdav.js', 'webdav.css']);
|
||||||
|
@ -1386,6 +1386,14 @@ namespace NanoKaraDAV
|
||||||
return new \DateTime('@' . fileatime($target));
|
return new \DateTime('@' . fileatime($target));
|
||||||
case 'DAV::creationdate':
|
case 'DAV::creationdate':
|
||||||
return new \DateTime('@' . filectime($target));
|
return new \DateTime('@' . filectime($target));
|
||||||
|
case 'http://owncloud.org/ns:permissions':
|
||||||
|
$permissions = 'G';
|
||||||
|
|
||||||
|
if (is_writeable($target) && !FORCE_READONLY) {
|
||||||
|
$permissions .= 'DNVWCK';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $permissions;
|
||||||
case WebDAV::PROP_DIGEST_MD5:
|
case WebDAV::PROP_DIGEST_MD5:
|
||||||
if (!is_file($target)) {
|
if (!is_file($target)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1430,6 +1438,10 @@ namespace NanoKaraDAV
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
$parent = dirname($target);
|
$parent = dirname($target);
|
||||||
|
|
||||||
|
@ -1485,12 +1497,20 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function delete(string $uri): void
|
public function delete(string $uri): void
|
||||||
{
|
{
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
|
|
||||||
if (!file_exists($target)) {
|
if (!file_exists($target)) {
|
||||||
throw new WebDAV_Exception('Target does not exist', 404);
|
throw new WebDAV_Exception('Target does not exist', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_writeable($target)) {
|
||||||
|
throw new WebDAV_Exception('File permissions says that you cannot delete this, sorry.', 403);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_dir($target)) {
|
if (is_dir($target)) {
|
||||||
foreach (self::glob($target, '/*') as $file) {
|
foreach (self::glob($target, '/*') as $file) {
|
||||||
$this->delete(substr($file, strlen($this->path)));
|
$this->delete(substr($file, strlen($this->path)));
|
||||||
|
@ -1505,6 +1525,10 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function copymove(bool $move, string $uri, string $destination): bool
|
public function copymove(bool $move, string $uri, string $destination): bool
|
||||||
{
|
{
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$source = $this->path . $uri;
|
$source = $this->path . $uri;
|
||||||
$target = $this->path . $destination;
|
$target = $this->path . $destination;
|
||||||
$parent = dirname($target);
|
$parent = dirname($target);
|
||||||
|
@ -1566,6 +1590,10 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function mkcol(string $uri): void
|
public function mkcol(string $uri): void
|
||||||
{
|
{
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
if (!disk_free_space($this->path)) {
|
if (!disk_free_space($this->path)) {
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
|
@ -1625,8 +1653,8 @@ namespace NanoKaraDAV
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
use NanoKaraDAV\Server;
|
use PicoDAV\Server;
|
||||||
use NanoKaraDAV\Storage;
|
use PicoDAV\Storage;
|
||||||
|
|
||||||
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
$root = substr(__DIR__, strlen($_SERVER['DOCUMENT_ROOT']));
|
$root = substr(__DIR__, strlen($_SERVER['DOCUMENT_ROOT']));
|
||||||
|
@ -1638,6 +1666,29 @@ namespace {
|
||||||
|
|
||||||
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG = [
|
||||||
|
'FORCE_READONLY' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$config = [];
|
||||||
|
|
||||||
|
if (file_exists(__DIR__ . '/.picodav.ini')) {
|
||||||
|
$config = parse_ini_file(__DIR__ . '/.picodav.ini');
|
||||||
|
$config = array_change_key_case($config, \CASE_UPPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DEFAULT_CONFIG as $key => $value) {
|
||||||
|
if (array_key_exists($key, $config)) {
|
||||||
|
$value = $config[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool(DEFAULT_CONFIG[$key])) {
|
||||||
|
$value = boolval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
define('PicoDAV\\' . $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
if ($relative_uri == 'webdav.js' || $relative_uri == 'webdav.css') {
|
if ($relative_uri == 'webdav.js' || $relative_uri == 'webdav.css') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
|
||||||
|
@ -1657,11 +1708,11 @@ namespace {
|
||||||
$fp = fopen(__FILE__, 'r');
|
$fp = fopen(__FILE__, 'r');
|
||||||
|
|
||||||
if ($relative_uri == 'webdav.js') {
|
if ($relative_uri == 'webdav.js') {
|
||||||
fseek($fp, 43873, SEEK_SET);
|
fseek($fp, 45051, SEEK_SET);
|
||||||
echo fread($fp, 24265);
|
echo fread($fp, 25889);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fseek($fp, 43873 + 24265, SEEK_SET);
|
fseek($fp, 45051 + 25889, SEEK_SET);
|
||||||
echo fread($fp, 6760);
|
echo fread($fp, 6760);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1694,8 +1745,8 @@ const WebDAVNavigator = (url, options) => {
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
const common_buttons = `<input class="rename" type="button" value="${_('Rename')}" />
|
const rename_button = `<input class="rename" type="button" value="${_('Rename')}" />`;
|
||||||
<input class="delete" type="button" value="${_('Delete')}" />`;
|
const delete_button = `<input class="delete" type="button" value="${_('Delete')}" />`;
|
||||||
|
|
||||||
const edit_button = `<input class="edit" type="button" value="${_('Edit')}" />`;
|
const edit_button = `<input class="edit" type="button" value="${_('Edit')}" />`;
|
||||||
|
|
||||||
|
@ -1718,10 +1769,6 @@ const WebDAVNavigator = (url, options) => {
|
||||||
|
|
||||||
const body_tpl = `<h1>%title%</h1>
|
const body_tpl = `<h1>%title%</h1>
|
||||||
<div class="upload">
|
<div class="upload">
|
||||||
<input class="mkdir" type="button" value="${_('New directory')}" />
|
|
||||||
<input type="file" style="display: none;" />
|
|
||||||
<input class="mkfile" type="button" value="${_('New text file')}" />
|
|
||||||
<input class="uploadfile" type="button" value="${_('Upload file')}" />
|
|
||||||
<select class="sortorder btn">
|
<select class="sortorder btn">
|
||||||
<option value="name">${_('Sort by name')}</option>
|
<option value="name">${_('Sort by name')}</option>
|
||||||
<option value="date">${_('Sort by date')}</option>
|
<option value="date">${_('Sort by date')}</option>
|
||||||
|
@ -1730,13 +1777,18 @@ const WebDAVNavigator = (url, options) => {
|
||||||
</div>
|
</div>
|
||||||
<table>%table%</table>`;
|
<table>%table%</table>`;
|
||||||
|
|
||||||
const dir_row_tpl = `<tr><td class="thumb"><span class="icon dir"><b>%icon%</b></span></td><th colspan="3"><a href="%uri%">%name%</a></th><td class="buttons"><div></div></td></tr>`;
|
const create_buttons = `<input class="mkdir" type="button" value="${_('New directory')}" />
|
||||||
const file_row_tpl = `<tr data-mime="%mime%"><td class="thumb"><span class="icon %icon%"><b>%icon%</b></span></td><th><a href="%uri%">%name%</a></th><td class="size">%size%</td><td>%modified%</td><td class="buttons"><div><a href="%uri%" download class="btn">${_('Download')}</a></div></td></tr>`;
|
<input type="file" style="display: none;" />
|
||||||
|
<input class="mkfile" type="button" value="${_('New text file')}" />
|
||||||
|
<input class="uploadfile" type="button" value="${_('Upload file')}" />`;
|
||||||
|
|
||||||
|
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%"><td class="thumb"><span class="icon %icon%"><b>%icon%</b></span></td><th><a href="%uri%">%name%</a></th><td class="size">%size%</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:">
|
<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/>
|
<D:getlastmodified/><D:getcontenttype/><D:getcontentlength/><D:resourcetype/><D:displayname/><oc:permissions/>
|
||||||
</D:prop>
|
</D:prop>
|
||||||
</D:propfind>`;
|
</D:propfind>`;
|
||||||
|
|
||||||
|
@ -2040,14 +2092,32 @@ const WebDAVNavigator = (url, options) => {
|
||||||
|
|
||||||
var items = [[], []];
|
var items = [[], []];
|
||||||
var title = null;
|
var title = null;
|
||||||
|
var root_permissions = null;
|
||||||
|
|
||||||
xml.querySelectorAll('response').forEach((node) => {
|
xml.querySelectorAll('response').forEach((node) => {
|
||||||
var item_uri = normalizeURL(node.querySelector('href').textContent);
|
var item_uri = normalizeURL(node.querySelector('href').textContent);
|
||||||
|
var props = null;
|
||||||
|
|
||||||
|
node.querySelectorAll('propstat').forEach((propstat) => {
|
||||||
|
if (propstat.querySelector('status').textContent.match(/200/)) {
|
||||||
|
props = propstat;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This item didn't return any properties, everything is 404?
|
||||||
|
if (!props) {
|
||||||
|
console.error('Cannot find properties for: ' + item_uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var name = item_uri.replace(/\/$/, '').split('/').pop();
|
var name = item_uri.replace(/\/$/, '').split('/').pop();
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
|
|
||||||
|
var permissions = (prop = node.querySelector('permissions')) ? prop.textContent : null;
|
||||||
|
|
||||||
if (item_uri == uri) {
|
if (item_uri == uri) {
|
||||||
title = name;
|
title = name;
|
||||||
|
root_permissions = permissions;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2061,6 +2131,7 @@ const WebDAVNavigator = (url, options) => {
|
||||||
'mime': !is_dir && (prop = node.querySelector('getcontenttype')) ? prop.textContent : null,
|
'mime': !is_dir && (prop = node.querySelector('getcontenttype')) ? prop.textContent : null,
|
||||||
'modified': (prop = node.querySelector('getlastmodified')) ? new Date(prop.textContent) : null,
|
'modified': (prop = node.querySelector('getlastmodified')) ? new Date(prop.textContent) : null,
|
||||||
'is_dir': is_dir,
|
'is_dir': is_dir,
|
||||||
|
'permissions': permissions,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2100,6 +2171,12 @@ const WebDAVNavigator = (url, options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
|
// Don't include files we cannot read
|
||||||
|
if (item.permissions !== null && item.permissions.indexOf('G') == -1) {
|
||||||
|
console.error('OC permissions deny read access to this file: ' + item.name, 'Permissions: ', item.permissions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var row = item.is_dir ? dir_row_tpl : file_row_tpl;
|
var row = item.is_dir ? dir_row_tpl : file_row_tpl;
|
||||||
item.size = item.size !== null ? formatBytes(item.size).replace(/ /g, ' ') : null;
|
item.size = item.size !== null ? formatBytes(item.size).replace(/ /g, ' ') : null;
|
||||||
item.icon = item.is_dir ? '📁' : (item.uri.indexOf('.') > 0 ? item.uri.replace(/^.*\.(\w+)$/, '$1').toUpperCase() : '');
|
item.icon = item.is_dir ? '📁' : (item.uri.indexOf('.') > 0 ? item.uri.replace(/^.*\.(\w+)$/, '$1').toUpperCase() : '');
|
||||||
|
@ -2111,13 +2188,76 @@ const WebDAVNavigator = (url, options) => {
|
||||||
document.title = title;
|
document.title = title;
|
||||||
document.querySelector('main').innerHTML = template(body_tpl, {'title': html(document.title), 'base_url': base_url, 'table': table});
|
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);
|
||||||
|
reloadListing();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!root_permissions || root_permissions.indexOf('CK') != -1) {
|
||||||
|
$('.upload').insertAdjacentHTML('afterbegin', create_buttons);
|
||||||
|
|
||||||
|
$('.mkdir').onclick = () => {
|
||||||
|
openDialog(mkdir_dialog);
|
||||||
|
document.forms[0].onsubmit = () => {
|
||||||
|
var name = $('input[name=mkdir]').value;
|
||||||
|
|
||||||
|
if (!name) return false;
|
||||||
|
|
||||||
|
name = encodeURIComponent(name);
|
||||||
|
|
||||||
|
req('MKCOL', current_url + name).then(() => openListing(current_url + name + '/'));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$('.mkfile').onclick = () => {
|
||||||
|
openDialog(mkfile_dialog);
|
||||||
|
var t = $('input[name=mkfile]');
|
||||||
|
t.value = '.md';
|
||||||
|
t.focus();
|
||||||
|
t.selectionStart = t.selectionEnd = 0;
|
||||||
|
document.forms[0].onsubmit = () => {
|
||||||
|
var name = t.value;
|
||||||
|
|
||||||
|
if (!name) return false;
|
||||||
|
|
||||||
|
name = encodeURIComponent(name);
|
||||||
|
|
||||||
|
return reqAndReload('PUT', current_url + name, '');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var fi = $('input[type=file]');
|
||||||
|
|
||||||
|
$('.uploadfile').onclick = () => fi.click();
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Array.from($('table').rows).forEach((tr) => {
|
Array.from($('table').rows).forEach((tr) => {
|
||||||
var $$ = (a) => tr.querySelector(a);
|
var $$ = (a) => tr.querySelector(a);
|
||||||
var file_url = $$('a').href;
|
var file_url = $$('a').href;
|
||||||
var file_name = $$('a').innerText;
|
var file_name = $$('a').innerText;
|
||||||
var dir = $$('[colspan]');
|
var dir = $$('[colspan]');
|
||||||
var mime = !dir ? tr.getAttribute('data-mime') : 'dir';
|
var mime = !dir ? tr.getAttribute('data-mime') : 'dir';
|
||||||
var buttons = $$('td.buttons div')
|
var buttons = $$('td.buttons div');
|
||||||
|
var permissions = tr.getAttribute('data-permissions');
|
||||||
|
|
||||||
|
if (permissions == 'null') {
|
||||||
|
permissions = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (dir) {
|
if (dir) {
|
||||||
$$('a').onclick = () => {
|
$$('a').onclick = () => {
|
||||||
|
@ -2130,6 +2270,7 @@ const WebDAVNavigator = (url, options) => {
|
||||||
if (dir && $$('a').getAttribute('href').length < uri.length) {
|
if (dir && $$('a').getAttribute('href').length < uri.length) {
|
||||||
dir.setAttribute('colspan', 4);
|
dir.setAttribute('colspan', 4);
|
||||||
tr.querySelector('td:last-child').remove();
|
tr.querySelector('td:last-child').remove();
|
||||||
|
tr.querySelector('td:last-child').remove();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2142,7 +2283,43 @@ const WebDAVNavigator = (url, options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add rename/delete buttons
|
// Add rename/delete buttons
|
||||||
buttons.insertAdjacentHTML('afterbegin', common_buttons);
|
if (!permissions || permissions.indexOf('NV') != -1) {
|
||||||
|
buttons.insertAdjacentHTML('afterbegin', rename_button);
|
||||||
|
|
||||||
|
$$('.rename').onclick = () => {
|
||||||
|
openDialog(rename_dialog);
|
||||||
|
let t = $('input[name=rename]');
|
||||||
|
t.value = file_name;
|
||||||
|
t.focus();
|
||||||
|
t.selectionStart = 0;
|
||||||
|
t.selectionEnd = file_name.lastIndexOf('.');
|
||||||
|
document.forms[0].onsubmit = () => {
|
||||||
|
var name = t.value;
|
||||||
|
|
||||||
|
if (!name) return false;
|
||||||
|
|
||||||
|
name = encodeURIComponent(name);
|
||||||
|
name = name.replace(/%2F/, '/');
|
||||||
|
|
||||||
|
var dest = current_url + name;
|
||||||
|
dest = normalizeURL(dest);
|
||||||
|
|
||||||
|
return reqAndReload('MOVE', file_url, '', {'Destination': dest});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permissions || permissions.indexOf('D') != -1) {
|
||||||
|
buttons.insertAdjacentHTML('afterbegin', delete_button);
|
||||||
|
|
||||||
|
$$('.delete').onclick = (e) => {
|
||||||
|
openDialog(delete_dialog);
|
||||||
|
document.forms[0].onsubmit = () => {
|
||||||
|
return reqAndReload('DELETE', file_url);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var view_url, edit_url;
|
var view_url, edit_url;
|
||||||
|
|
||||||
|
@ -2180,135 +2357,53 @@ const WebDAVNavigator = (url, options) => {
|
||||||
$$('a').download = file_name;
|
$$('a').download = file_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mime.match(/^text\/|application\/x-empty/)) {
|
if (!permissions || permissions.indexOf('W') != -1) {
|
||||||
buttons.insertAdjacentHTML('beforeend', edit_button);
|
if ( mime.match(/^text\/|application\/x-empty/)) {
|
||||||
|
buttons.insertAdjacentHTML('beforeend', edit_button);
|
||||||
|
|
||||||
$$('.edit').onclick = (e) => {
|
$$('.edit').onclick = (e) => {
|
||||||
req('GET', file_url).then((r) => r.text().then((t) => {
|
req('GET', file_url).then((r) => r.text().then((t) => {
|
||||||
let md = file_url.match(/\.md$/);
|
let md = file_url.match(/\.md$/);
|
||||||
openDialog(md ? markdown_dialog : edit_dialog);
|
openDialog(md ? markdown_dialog : edit_dialog);
|
||||||
var txt = $('textarea[name=edit]');
|
var txt = $('textarea[name=edit]');
|
||||||
txt.value = t;
|
txt.value = t;
|
||||||
|
|
||||||
// Markdown editor
|
// Markdown editor
|
||||||
if (md) {
|
if (md) {
|
||||||
let pre = $('#md');
|
let pre = $('#md');
|
||||||
|
|
||||||
txt.oninput = () => {
|
txt.oninput = () => {
|
||||||
pre.innerHTML = microdown.parse(html(txt.value));
|
pre.innerHTML = microdown.parse(html(txt.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
txt.oninput();
|
||||||
|
|
||||||
|
// Sync scroll, not perfect but better than nothing
|
||||||
|
txt.onscroll = (e) => {
|
||||||
|
var p = e.target.scrollTop / (e.target.scrollHeight - e.target.offsetHeight);
|
||||||
|
var target = e.target == pre ? txt : pre;
|
||||||
|
target.scrollTop = p * (target.scrollHeight - target.offsetHeight);
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
document.forms[0].onsubmit = () => {
|
||||||
|
var content = txt.value;
|
||||||
|
|
||||||
|
return reqAndReload('PUT', file_url, content);
|
||||||
};
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (edit_url = wopi_getEditURL(file_url, mime)) {
|
||||||
|
buttons.insertAdjacentHTML('beforeend', edit_button);
|
||||||
|
|
||||||
txt.oninput();
|
$$('.icon').classList.add('document');
|
||||||
|
$$('.edit').onclick = () => { wopi_open(file_url, edit_url); return false; };
|
||||||
// Sync scroll, not perfect but better than nothing
|
}
|
||||||
txt.onscroll = (e) => {
|
|
||||||
var p = e.target.scrollTop / (e.target.scrollHeight - e.target.offsetHeight);
|
|
||||||
var target = e.target == pre ? txt : pre;
|
|
||||||
target.scrollTop = p * (target.scrollHeight - target.offsetHeight);
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
document.forms[0].onsubmit = () => {
|
|
||||||
var content = txt.value;
|
|
||||||
|
|
||||||
return reqAndReload('PUT', file_url, content);
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (edit_url = wopi_getEditURL(file_url, mime)) {
|
|
||||||
buttons.insertAdjacentHTML('beforeend', edit_button);
|
|
||||||
|
|
||||||
$$('.icon').classList.add('document');
|
|
||||||
$$('.edit').onclick = () => { wopi_open(file_url, edit_url); return false; };
|
|
||||||
}
|
|
||||||
|
|
||||||
$$('.delete').onclick = (e) => {
|
|
||||||
openDialog(delete_dialog);
|
|
||||||
document.forms[0].onsubmit = () => {
|
|
||||||
return reqAndReload('DELETE', file_url);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$$('.rename').onclick = () => {
|
|
||||||
openDialog(rename_dialog);
|
|
||||||
let t = $('input[name=rename]');
|
|
||||||
t.value = file_name;
|
|
||||||
t.focus();
|
|
||||||
t.selectionStart = 0;
|
|
||||||
t.selectionEnd = file_name.lastIndexOf('.');
|
|
||||||
document.forms[0].onsubmit = () => {
|
|
||||||
var name = t.value;
|
|
||||||
|
|
||||||
if (!name) return false;
|
|
||||||
|
|
||||||
name = encodeURIComponent(name);
|
|
||||||
name = name.replace(/%2F/, '/');
|
|
||||||
|
|
||||||
var dest = current_url + name;
|
|
||||||
dest = normalizeURL(dest);
|
|
||||||
|
|
||||||
return reqAndReload('MOVE', file_url, '', {'Destination': dest});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.mkdir').onclick = () => {
|
|
||||||
openDialog(mkdir_dialog);
|
|
||||||
document.forms[0].onsubmit = () => {
|
|
||||||
var name = $('input[name=mkdir]').value;
|
|
||||||
|
|
||||||
if (!name) return false;
|
|
||||||
|
|
||||||
name = encodeURIComponent(name);
|
|
||||||
|
|
||||||
req('MKCOL', current_url + name).then(() => openListing(current_url + name + '/'));
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.mkfile').onclick = () => {
|
|
||||||
openDialog(mkfile_dialog);
|
|
||||||
var t = $('input[name=mkfile]');
|
|
||||||
t.value = '.md';
|
|
||||||
t.focus();
|
|
||||||
t.selectionStart = t.selectionEnd = 0;
|
|
||||||
document.forms[0].onsubmit = () => {
|
|
||||||
var name = t.value;
|
|
||||||
|
|
||||||
if (!name) return false;
|
|
||||||
|
|
||||||
name = encodeURIComponent(name);
|
|
||||||
|
|
||||||
return reqAndReload('PUT', current_url + name, '');
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var select = $('.sortorder');
|
|
||||||
select.value = sort_order;
|
|
||||||
select.onchange = () => {
|
|
||||||
sort_order = select.value;
|
|
||||||
window.localStorage.setItem('sort_order', sort_order);
|
|
||||||
reloadListing();
|
|
||||||
};
|
|
||||||
|
|
||||||
var fi = $('input[type=file]');
|
|
||||||
|
|
||||||
$('.uploadfile').onclick = () => fi.click();
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var current_url = url;
|
var current_url = url;
|
||||||
|
|
83
server.php
83
server.php
|
@ -7,7 +7,7 @@ namespace KD2\WebDAV
|
||||||
//__KD2\WebDAV\AbstractStorage__
|
//__KD2\WebDAV\AbstractStorage__
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace NanoKaraDAV
|
namespace PicoDAV
|
||||||
{
|
{
|
||||||
use KD2\WebDAV\AbstractStorage;
|
use KD2\WebDAV\AbstractStorage;
|
||||||
use KD2\WebDAV\Exception as WebDAV_Exception;
|
use KD2\WebDAV\Exception as WebDAV_Exception;
|
||||||
|
@ -44,7 +44,7 @@ namespace NanoKaraDAV
|
||||||
$files = array_diff($files, $dirs);
|
$files = array_diff($files, $dirs);
|
||||||
|
|
||||||
// Remove PHP files from listings
|
// Remove PHP files from listings
|
||||||
$files = array_filter($files, fn($a) => !preg_match('/\.(?:php\d?|phtml|phps)$/i', $a));
|
$files = array_filter($files, fn($a) => !preg_match('/\.(?:php\d?|phtml|phps)$|^\./i', $a));
|
||||||
|
|
||||||
if (!$uri) {
|
if (!$uri) {
|
||||||
$files = array_diff($files, ['webdav.js', 'webdav.css']);
|
$files = array_diff($files, ['webdav.js', 'webdav.css']);
|
||||||
|
@ -59,6 +59,10 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function get(string $uri): ?array
|
public function get(string $uri): ?array
|
||||||
{
|
{
|
||||||
|
if (substr(basename($uri), 0, 1) == '.') {
|
||||||
|
throw new WebDAV_Exception('Invalid filename', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$path = $this->path . $uri;
|
$path = $this->path . $uri;
|
||||||
|
|
||||||
if (!file_exists($path)) {
|
if (!file_exists($path)) {
|
||||||
|
@ -110,6 +114,14 @@ namespace NanoKaraDAV
|
||||||
return new \DateTime('@' . fileatime($target));
|
return new \DateTime('@' . fileatime($target));
|
||||||
case 'DAV::creationdate':
|
case 'DAV::creationdate':
|
||||||
return new \DateTime('@' . filectime($target));
|
return new \DateTime('@' . filectime($target));
|
||||||
|
case 'http://owncloud.org/ns:permissions':
|
||||||
|
$permissions = 'G';
|
||||||
|
|
||||||
|
if (is_writeable($target) && !FORCE_READONLY) {
|
||||||
|
$permissions .= 'DNVWCK';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $permissions;
|
||||||
case WebDAV::PROP_DIGEST_MD5:
|
case WebDAV::PROP_DIGEST_MD5:
|
||||||
if (!is_file($target)) {
|
if (!is_file($target)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -154,6 +166,14 @@ namespace NanoKaraDAV
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr(basename($uri), 0, 1) == '.') {
|
||||||
|
throw new WebDAV_Exception('Invalid filename', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
$parent = dirname($target);
|
$parent = dirname($target);
|
||||||
|
|
||||||
|
@ -209,12 +229,24 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function delete(string $uri): void
|
public function delete(string $uri): void
|
||||||
{
|
{
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr(basename($uri), 0, 1) == '.') {
|
||||||
|
throw new WebDAV_Exception('Invalid filename', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$target = $this->path . $uri;
|
$target = $this->path . $uri;
|
||||||
|
|
||||||
if (!file_exists($target)) {
|
if (!file_exists($target)) {
|
||||||
throw new WebDAV_Exception('Target does not exist', 404);
|
throw new WebDAV_Exception('Target does not exist', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_writeable($target)) {
|
||||||
|
throw new WebDAV_Exception('File permissions says that you cannot delete this, sorry.', 403);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_dir($target)) {
|
if (is_dir($target)) {
|
||||||
foreach (self::glob($target, '/*') as $file) {
|
foreach (self::glob($target, '/*') as $file) {
|
||||||
$this->delete(substr($file, strlen($this->path)));
|
$this->delete(substr($file, strlen($this->path)));
|
||||||
|
@ -229,6 +261,18 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function copymove(bool $move, string $uri, string $destination): bool
|
public function copymove(bool $move, string $uri, string $destination): bool
|
||||||
{
|
{
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr(basename($uri), 0, 1) == '.') {
|
||||||
|
throw new WebDAV_Exception('Invalid filename', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr(basename($destination), 0, 1) == '.') {
|
||||||
|
throw new WebDAV_Exception('Invalid filename', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$source = $this->path . $uri;
|
$source = $this->path . $uri;
|
||||||
$target = $this->path . $destination;
|
$target = $this->path . $destination;
|
||||||
$parent = dirname($target);
|
$parent = dirname($target);
|
||||||
|
@ -290,6 +334,14 @@ namespace NanoKaraDAV
|
||||||
|
|
||||||
public function mkcol(string $uri): void
|
public function mkcol(string $uri): void
|
||||||
{
|
{
|
||||||
|
if (FORCE_READONLY) {
|
||||||
|
throw new WebDAV_Exception('Write access is disabled', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr(basename($uri), 0, 1) == '.') {
|
||||||
|
throw new WebDAV_Exception('Invalid filename', 403);
|
||||||
|
}
|
||||||
|
|
||||||
if (!disk_free_space($this->path)) {
|
if (!disk_free_space($this->path)) {
|
||||||
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
throw new WebDAV_Exception('Your quota is exhausted', 403);
|
||||||
}
|
}
|
||||||
|
@ -349,8 +401,8 @@ namespace NanoKaraDAV
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
use NanoKaraDAV\Server;
|
use PicoDAV\Server;
|
||||||
use NanoKaraDAV\Storage;
|
use PicoDAV\Storage;
|
||||||
|
|
||||||
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
$uri = strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
$root = substr(__DIR__, strlen($_SERVER['DOCUMENT_ROOT']));
|
$root = substr(__DIR__, strlen($_SERVER['DOCUMENT_ROOT']));
|
||||||
|
@ -362,6 +414,29 @@ namespace {
|
||||||
|
|
||||||
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
$relative_uri = ltrim(substr($uri, strlen($root)), '/');
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG = [
|
||||||
|
'FORCE_READONLY' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$config = [];
|
||||||
|
|
||||||
|
if (file_exists(__DIR__ . '/.picodav.ini')) {
|
||||||
|
$config = parse_ini_file(__DIR__ . '/.picodav.ini');
|
||||||
|
$config = array_change_key_case($config, \CASE_UPPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DEFAULT_CONFIG as $key => $value) {
|
||||||
|
if (array_key_exists($key, $config)) {
|
||||||
|
$value = $config[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool(DEFAULT_CONFIG[$key])) {
|
||||||
|
$value = boolval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
define('PicoDAV\\' . $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
if ($relative_uri == 'webdav.js' || $relative_uri == 'webdav.css') {
|
if ($relative_uri == 'webdav.js' || $relative_uri == 'webdav.css') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue