Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
14 KB
Referenced Files
None
Subscribers
None
diff --git a/src/app/Http/Controllers/DAVController.php b/src/app/Http/Controllers/DAVController.php
index f8d030f0..5e88ebf2 100644
--- a/src/app/Http/Controllers/DAVController.php
+++ b/src/app/Http/Controllers/DAVController.php
@@ -1,83 +1,88 @@
<?php
namespace App\Http\Controllers;
use App\Http\DAV;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Sabre\DAV\Server;
use Symfony\Component\HttpFoundation\StreamedResponse;
class DAVController extends Controller
{
/**
* Register WebDAV route(s)
*/
public static function registerRoutes(): void
{
$root = trim(\config('services.dav.webdav_root'), '/');
Route::match(
[
// Standard HTTP methods
'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT',
// WebDAV specific methods
'MOVE', 'COPY', 'MKCOL', 'PROPFIND', 'PROPPATCH', 'REPORT', 'LOCK', 'UNLOCK',
],
$root . '/user/{email}/{path?}',
[self::class, 'run']
)
->where('path', '.*') // This makes 'path' to match also sub-paths
->name('dav');
}
/**
* Handle a WebDAV request
*/
public function run(Request $request, string $email): Response|StreamedResponse
{
$root = trim(\config('services.dav.webdav_root'), '/');
$sapi = new DAV\Sapi();
$auth_backend = new DAV\Auth();
$locks_backend = new DAV\Locks();
// Initialize the Sabre DAV Server
$server = new Server(new DAV\Collection(''), $sapi);
$server->setBaseUri('/' . $root . '/user/' . $email);
$server->debugExceptions = \config('app.debug');
$server->enablePropfindDepthInfinity = false;
$server::$exposeVersion = false;
// FIXME: Streaming is supposed to improve memory use, but it changes
// how the response is handled in a way that e.g. for an unknown location you get
// 207 instead of 404. And our response handling is not working with this either.
// $server::$streamMultiStatus = true;
// Log important exceptions catched by Sabre
$server->on('exception', function ($e) {
if (!($e instanceof \Sabre\DAV\Exception) || $e->getHTTPCode() == 500) {
\Log::error($e);
}
+ // Rollback uncommitted transactions
+ while (DB::transactionLevel() > 0) {
+ DB::rollBack();
+ }
});
// Register some plugins
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($auth_backend));
// Unauthenticated access doesn't work for us since we require credentials to get access to the data in the first place.
// $acl_plugin = new \Sabre\DAVACL\Plugin();
// $acl_plugin->allowUnauthenticatedAccess = false;
// $server->addPlugin($acl_plugin);
// The lock manager is responsible for making sure users don't overwrite each others changes.
$server->addPlugin(new \Sabre\DAV\Locks\Plugin($locks_backend));
// Intercept some of the garbage files operating systems tend to generate when mounting a WebDAV share
// $server->addPlugin(new DAV\TempFiles());
// Finally, process the request
$server->start();
return $sapi->getResponse();
}
}
diff --git a/src/app/Http/DAV/Collection.php b/src/app/Http/DAV/Collection.php
index 87e74181..22428518 100644
--- a/src/app/Http/DAV/Collection.php
+++ b/src/app/Http/DAV/Collection.php
@@ -1,366 +1,366 @@
<?php
namespace App\Http\DAV;
use App\Backends\Storage;
use App\Fs\Item;
use Illuminate\Support\Facades\DB;
use Sabre\DAV\Exception;
use Sabre\DAV\ICollection;
use Sabre\DAV\ICopyTarget;
use Sabre\DAV\IMoveTarget;
use Sabre\DAV\INode;
use Sabre\DAV\INodeByPath;
use Sabre\DAV\IProperties;
/**
* Sabre DAV Collection interface implemetation
*/
class Collection extends Node implements ICollection, ICopyTarget, IMoveTarget, INodeByPath, IProperties
{
/**
* Checks if a child-node exists.
*
* It is generally a good idea to try and override this. Usually it can be optimized.
*
* @param string $name
*
* @return bool
*/
public function childExists($name)
{
$path = $this->nodePath($name);
\Log::debug('[DAV] CHILD-EXISTS: ' . $path);
try {
$this->fsItemForPath($path);
return true;
} catch (\Sabre\DAV\Exception\NotFound $e) {
return false;
}
}
/**
* Copies a node into this collection.
*
* It is up to the implementors to:
* 1. Create the new resource.
* 2. Copy the data and any properties.
*
* If you return true from this function, the assumption
* is that the copy was successful.
* If you return false, sabre/dav will handle the copy itself.
*
* @param string $targetName New local file/collection name
* @param string $sourcePath Full path to source node
* @param INode $sourceNode Source node itself
*
* @return bool
*/
public function copyInto($targetName, $sourcePath, INode $sourceNode)
{
$path = $this->nodePath($targetName);
\Log::debug("[DAV] COPY-INTO: {$sourcePath} > {$path}");
$item = $sourceNode->fsItem(); // @phpstan-ignore-line
// TODO: Missing Depth:X handling. See also https://github.com/sabre-io/dav/pull/1495
$item->copy($this->data, $targetName);
$this->deleteCachedItem($path);
return true;
}
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After succesful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
*
* @return string|null
*
* @throws Exception
*/
public function createFile($name, $data = null)
{
$path = $this->nodePath($name);
\Log::debug('[DAV] CREATE-FILE: ' . $path);
if (str_starts_with($name, '.')) {
throw new Exception\Forbidden('Hidden files are not accepted');
}
DB::beginTransaction();
$file = Auth::$user->fsItems()->create(['type' => Item::TYPE_FILE]);
$file->setProperty('name', $name);
if ($parent = $this->data) {
$parent->children()->attach($file->id);
}
// FIXME: For big files fileInput() can take a while, so use of transactions here might be a problem, or not?
// FIXME: fileInput() will detect input mimetype, should we rather trust the request Content-Type?
Storage::fileInput($data, [], $file);
DB::commit();
// Refresh the file to get an up-to-date ETag
$file = new File($path, $this, $file);
$file->refresh();
return $file->getETag();
}
/**
* Creates a new subdirectory
*
* @param string $name
*
* @throws Exception
*/
public function createDirectory($name)
{
\Log::debug('[DAV] CREATE-DIRECTORY: ' . $this->nodePath($name));
if (str_starts_with($name, '.')) {
throw new Exception\Forbidden('Hidden files are not accepted');
}
DB::beginTransaction();
$collection = Auth::$user->fsItems()->create(['type' => Item::TYPE_COLLECTION]);
$collection->setProperty('name', $name);
if ($parent = $this->data) {
$parent->children()->attach($collection->id);
}
DB::commit();
}
/**
* Deletes the current collection
*
* @throws \Exception
*/
public function delete()
{
- parent::delete();
-
DB::beginTransaction();
+ parent::delete();
+
// Delete the files/folders inside
// TODO: This may not be optimal for a case with a lot of files/folders
// TODO: Maybe deleting a folder contents should be moved to a delete event observer
$this->data->children()->where('type', Item::TYPE_COLLECTION)
->select('fs_items.*')
->selectRaw('(select value from fs_properties where fs_items.id = fs_properties.item_id'
. ' and fs_properties.key = \'name\') as name')
->get()
->each(function ($folder) {
$path = $this->nodePath($folder->name); // @phpstan-ignore-line
$node = new self($path, $this, $folder);
$node->delete();
});
$this->data->children()->delete();
DB::commit();
}
/**
* Returns an array with all the child nodes.
*
* @return INode[]
*
* @throws \Exception
*/
public function getChildren()
{
\Log::debug('[DAV] GET-CHILDREN: ' . $this->path);
$query = Auth::$user->fsItems()
->select('fs_items.*')
->whereNot('type', '&', Item::TYPE_INCOMPLETE);
foreach (['name', 'size', 'mimetype'] as $key) {
$query->selectRaw('(select value from fs_properties where fs_items.id = fs_properties.item_id'
. " and fs_properties.key = '{$key}') as {$key}");
}
if ($parent = $this->data) {
$query->join('fs_relations', 'fs_items.id', '=', 'fs_relations.related_id')
->where('fs_relations.item_id', $parent->id);
} else {
$query->leftJoin('fs_relations', 'fs_items.id', '=', 'fs_relations.related_id')
->whereNull('fs_relations.related_id');
}
return $query->orderBy('name')
->get()
->map(function ($item) {
$class = $item->type == Item::TYPE_COLLECTION ? Collection::class : File::class;
return new $class($this->nodePath($item), $this, $item);
})
->all();
}
/**
* Returns a child object, by its name.
*
* Generally its wise to override this, as this can usually be optimized
*
* This method must throw Sabre\DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
*
* @return INode
*
* @throws \Exception
*/
public function getChild($name)
{
return $this->getNodeForPath($name);
}
/**
* Returns the INode object for the requested path.
*
* In case where this collection can not retrieve the requested node
* but also can not determine that the node does not exists,
* null should be returned to signal that the caller should fallback
* to walking the directory tree.
*
* @param string $name Node name (can include path)
*
* @return INode|null
*
* @throws \Exception
*/
public function getNodeForPath($name)
{
$path = $this->nodePath($name);
\Log::debug("[DAV] GET-NODE-FOR-PATH: {$path}");
if (str_starts_with($name, '.') || str_contains($name, '/.')) {
throw new Exception\NotFound("File not found: {$path}");
}
$item = $this->fsItemForPath($path);
$class = $item->type == Item::TYPE_COLLECTION ? self::class : File::class;
$parent = $this;
$parent_path = preg_replace('|/[^/]+$|', '', $path);
if ($parent_path != $this->path) {
$parent = $this->fsItemForPath($parent_path);
}
return new $class($path, $parent, $item);
}
/**
* Returns a list of properties for this node.
*
* The properties list is a list of property names the client requested,
* encoded in clark-notation {xmlnamespace}tagname
*
* If the array is empty, it means 'all properties' were requested.
*
* Note that it's fine to liberally give properties back, instead of
* conforming to the list of requested properties.
* The Server class will filter out the extra.
*
* @param array $properties
*
* @return array
*/
public function getProperties($properties)
{
$result = [];
if (!empty($this->data->created_at)) {
$result['{DAV:}creationdate'] = \Sabre\HTTP\toDate($this->data->created_at);
}
return $result;
}
/**
* Moves a node into this collection.
*
* It is up to the implementors to:
* 1. Create the new resource.
* 2. Remove the old resource.
* 3. Transfer any properties or other data.
*
* Generally you should make very sure that your collection can easily move
* the node.
*
* If you don't, just return false, which will trigger sabre/dav to handle
* the move itself. If you return true from this function, the assumption
* is that the move was successful.
*
* @param string $targetName New local file/collection name
* @param string $sourcePath Full path to source node
* @param INode $sourceNode Source node itself
*
* @return bool
*/
public function moveInto($targetName, $sourcePath, INode $sourceNode)
{
$path = $this->nodePath($targetName);
\Log::debug("[DAV] MOVE-INTO: {$sourcePath} > {$path}");
$item = $sourceNode->fsItem(); // @phpstan-ignore-line
$item->move($this->data, $targetName);
$this->deleteCachedItem($path);
return true;
}
/**
* Updates properties on this node.
*
* This method received a PropPatch object, which contains all the
* information about the update.
*
* To update specific properties, call the 'handle' method on this object.
* Read the PropPatch documentation for more information.
*/
public function propPatch(\Sabre\DAV\PropPatch $propPatch)
{
\Log::debug('[DAV] PROP-PATCH: ' . $this->path);
// not supported
// FIXME: Should we throw an exception?
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 8:57 AM (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823361
Default Alt Text
(14 KB)

Event Timeline