Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117880987
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rK kolab
Attached
Detach File
Event Timeline