Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None
diff --git a/src/app/Http/Controllers/DAVController.php b/src/app/Http/Controllers/DAVController.php
index 5e88ebf2..e424ce35 100644
--- a/src/app/Http/Controllers/DAVController.php
+++ b/src/app/Http/Controllers/DAVController.php
@@ -1,88 +1,89 @@
<?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) {
+ // Log important exceptions catched by Sabre
if (!($e instanceof \Sabre\DAV\Exception) || $e->getHTTPCode() == 500) {
\Log::error($e);
}
- // Rollback uncommitted transactions
+ // Rollback uncommitted transactions. The exceptions are catched by Sabre
+ // so our Laravel exception handler is not triggered.
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/Sapi.php b/src/app/Http/DAV/Sapi.php
index 81c64bd4..0a627417 100644
--- a/src/app/Http/DAV/Sapi.php
+++ b/src/app/Http/DAV/Sapi.php
@@ -1,133 +1,148 @@
<?php
namespace App\Http\DAV;
use Sabre\HTTP\Request;
use Sabre\HTTP\ResponseInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Sabre SAPI implementation that uses Laravel request/response for input/output.
*/
class Sapi extends \Sabre\HTTP\Sapi
{
private static $response;
/**
* This static method will create a new Request object, based on the current PHP request.
*/
public static function getRequest(): Request
{
$request = \request();
$headers = [];
foreach ($request->headers as $key => $val) {
if (is_array($val) && !in_array($key, ['php-auth-user', 'php-auth-pw'])) {
$headers[$key] = implode("\n", $val);
}
}
// TODO: For now we create the Sabre's Request object. For better performance
// and memory usage we should replece it completely with a "direct" access to Laravel's Request.
$r = new Request($request->method(), $request->path(), $headers);
$r->setHttpVersion('1.1');
// $r->setRawServerData($_SERVER);
$r->setAbsoluteUrl($request->url());
$r->setBody($body = $request->getContent(true));
$r->setPostData($request->all());
// Input debug logging
if (\config('app.debug')) {
$msg = sprintf("[DAV] %s %s\n", $request->method(), $request->path());
foreach ($headers as $key => $val) {
if ($key == 'authorization') {
$msg .= 'Authorization: ' . explode(' ', $val, 2)[0] . " ***\n";
} else {
$msg .= preg_replace_callback('/(^|-)[a-z]/', fn ($m) => strtoupper($m[0]), $key) . ": {$val}\n";
}
}
if (!\request()->isMethod('put')) {
- $msg .= "\n" . stream_get_contents($body);
- rewind($body);
- // TODO: Format XML
+ if ($str = stream_get_contents($body)) {
+ $msg .= "\n" . self::formatXML($str);
+ rewind($body);
+ }
}
- \Log::debug($msg);
+ \Log::debug(rtrim($msg));
}
return $r;
}
/**
* Laravel Response object getter. To be called after Sapi::sendResponse()
*
* This method is for Kolab only, is not part of the Sabre SAPI interface.
*/
public function getResponse()
{
return self::$response;
}
/**
* Override Sabre's Sapi HTTP response sending. Create Laravel's Response
* to be returned from getResponse()
*/
public static function sendResponse(ResponseInterface $response): void
{
$callback = function () use ($response): void {
$body = $response->getBody();
if ($body === null || is_string($body)) {
echo $body;
} elseif (is_callable($body)) {
// FIXME: A callable seems to be used only with streamMultiStatus=true
$body();
} elseif (is_resource($body)) {
$content_length = $response->getHeader('Content-Length');
$length = is_numeric($content_length) ? (int) $content_length : null;
if ($length === null) {
while (!feof($body)) {
echo fread($body, 10 * 1024 * 1024);
}
} else {
while ($length > 0 && !feof($body)) {
$output = fread($body, min($length, 10 * 1024 * 1024));
$length -= strlen($output);
echo $output;
}
}
fclose($body);
}
};
// Output debug logging
if (\config('app.debug')) {
$msg = sprintf("[DAV] HTTP/%s %s %s\n", $response->getHttpVersion(), $response->getStatus(), $response->getSTatusText());
foreach ($response->getHeaders() as $key => $val) {
$msg .= $key . ": " . implode("\n", $val) . "\n";
}
if (!\request()->isMethod('get')) {
$body = $response->getBody();
- $msg .= "\n";
+
if (is_resource($body)) {
- $msg .= stream_get_contents($body);
+ $str = stream_get_contents($body);
rewind($body);
- } else {
- $msg .= $body;
+ $body = $str;
+ }
+
+ if ($body) {
+ $msg .= "\n" . self::formatXML($body);
}
- // TODO: Format XML
}
- \Log::debug($msg);
+ \Log::debug(rtrim($msg));
}
// FIXME: Should we use non-streamed responses for small bodies?
self::$response = new StreamedResponse($callback, $response->getStatus(), $response->getHeaders());
}
+
+ /**
+ * Convert XML into human-readable format (wrap lines).
+ */
+ private static function formatXML(string $xml): string
+ {
+ $doc = new \DOMDocument('1.0', 'UTF-8');
+ $doc->loadXML($xml);
+ $doc->formatOutput = true;
+
+ return $doc->saveXML() ?: '';
+ }
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Apr 5, 11:37 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831595
Default Alt Text
(9 KB)

Event Timeline