@@ -23,268 +23,268 @@ |
||
| 23 | 23 | * @see \Sabre\DAV\Server |
| 24 | 24 | */ |
| 25 | 25 | class Server extends \Sabre\DAV\Server { |
| 26 | - /** @var CachingTree $tree */ |
|
| 27 | - |
|
| 28 | - /** |
|
| 29 | - * Tracks queries done by plugins. |
|
| 30 | - * @var array<string, array<int, array<string, array{nodes:int, |
|
| 31 | - * queries:int}>>> The keys represent: event name, depth and plugin name |
|
| 32 | - */ |
|
| 33 | - private array $pluginQueries = []; |
|
| 34 | - |
|
| 35 | - public bool $debugEnabled = false; |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * @var array<string, array<int, callable>> |
|
| 39 | - */ |
|
| 40 | - private array $originalListeners = []; |
|
| 41 | - |
|
| 42 | - /** |
|
| 43 | - * @var array<string, array<int, callable>> |
|
| 44 | - */ |
|
| 45 | - private array $wrappedListeners = []; |
|
| 46 | - |
|
| 47 | - /** |
|
| 48 | - * @see \Sabre\DAV\Server |
|
| 49 | - */ |
|
| 50 | - public function __construct($treeOrNode = null) { |
|
| 51 | - parent::__construct($treeOrNode); |
|
| 52 | - self::$exposeVersion = false; |
|
| 53 | - $this->enablePropfindDepthInfinity = true; |
|
| 54 | - } |
|
| 55 | - |
|
| 56 | - #[Override] |
|
| 57 | - public function once( |
|
| 58 | - string $eventName, |
|
| 59 | - callable $callBack, |
|
| 60 | - int $priority = 100, |
|
| 61 | - ): void { |
|
| 62 | - $this->debugEnabled ? $this->monitorPropfindQueries( |
|
| 63 | - parent::once(...), |
|
| 64 | - ...\func_get_args(), |
|
| 65 | - ) : parent::once(...\func_get_args()); |
|
| 66 | - } |
|
| 67 | - |
|
| 68 | - #[Override] |
|
| 69 | - public function on( |
|
| 70 | - string $eventName, |
|
| 71 | - callable $callBack, |
|
| 72 | - int $priority = 100, |
|
| 73 | - ): void { |
|
| 74 | - $this->debugEnabled ? $this->monitorPropfindQueries( |
|
| 75 | - parent::on(...), |
|
| 76 | - ...\func_get_args(), |
|
| 77 | - ) : parent::on(...\func_get_args()); |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - /** |
|
| 81 | - * Wraps the handler $callBack into a query-monitoring function and calls |
|
| 82 | - * $parentFn to register it. |
|
| 83 | - */ |
|
| 84 | - private function monitorPropfindQueries( |
|
| 85 | - callable $parentFn, |
|
| 86 | - string $eventName, |
|
| 87 | - callable $callBack, |
|
| 88 | - int $priority = 100, |
|
| 89 | - ): void { |
|
| 90 | - $pluginName = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['class'] ?? 'unknown'; |
|
| 91 | - // The NotifyPlugin needs to be excluded as it emits the |
|
| 92 | - // `preloadCollection` event, which causes many plugins run queries. |
|
| 93 | - /** @psalm-suppress TypeDoesNotContainType */ |
|
| 94 | - if ($pluginName === PropFindPreloadNotifyPlugin::class || ($eventName !== 'propFind' |
|
| 95 | - && $eventName !== 'preloadCollection')) { |
|
| 96 | - $parentFn($eventName, $callBack, $priority); |
|
| 97 | - return; |
|
| 98 | - } |
|
| 99 | - |
|
| 100 | - $wrappedCallback |
|
| 101 | - = $this->getMonitoredCallback($callBack, $pluginName, $eventName); |
|
| 102 | - $this->originalListeners[$eventName][] = $callBack; |
|
| 103 | - $this->wrappedListeners[$eventName][] = $wrappedCallback; |
|
| 104 | - |
|
| 105 | - $parentFn($eventName, $wrappedCallback, $priority); |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - public function removeListener( |
|
| 109 | - string $eventName, |
|
| 110 | - callable $listener, |
|
| 111 | - ): bool { |
|
| 112 | - $listenerIndex = null; |
|
| 113 | - if (isset($this->wrappedListeners[$eventName], $this->originalListeners[$eventName])) { |
|
| 114 | - $key = array_search( |
|
| 115 | - $listener, |
|
| 116 | - $this->originalListeners[$eventName], |
|
| 117 | - true |
|
| 118 | - ); |
|
| 119 | - if ($key !== false) { |
|
| 120 | - $listenerIndex = $key; |
|
| 121 | - $listener = $this->wrappedListeners[$eventName][$listenerIndex]; |
|
| 122 | - } |
|
| 123 | - } |
|
| 124 | - $removed = parent::removeListener($eventName, $listener); |
|
| 125 | - |
|
| 126 | - if ($removed && $listenerIndex !== null) { |
|
| 127 | - unset($this->originalListeners[$eventName][$listenerIndex], $this->wrappedListeners[$eventName][$listenerIndex]); |
|
| 128 | - } |
|
| 129 | - |
|
| 130 | - return $removed; |
|
| 131 | - } |
|
| 132 | - |
|
| 133 | - public function removeAllListeners(?string $eventName = null): void { |
|
| 134 | - parent::removeAllListeners($eventName); |
|
| 135 | - |
|
| 136 | - if ($eventName === null) { |
|
| 137 | - $this->originalListeners = []; |
|
| 138 | - $this->wrappedListeners = []; |
|
| 139 | - } else { |
|
| 140 | - unset($this->wrappedListeners[$eventName], $this->originalListeners[$eventName]); |
|
| 141 | - } |
|
| 142 | - } |
|
| 143 | - |
|
| 144 | - /** |
|
| 145 | - * Returns a callable that wraps $callBack with code that monitors and |
|
| 146 | - * records queries per plugin. |
|
| 147 | - */ |
|
| 148 | - private function getMonitoredCallback( |
|
| 149 | - callable $callBack, |
|
| 150 | - string $pluginName, |
|
| 151 | - string $eventName, |
|
| 152 | - ): callable { |
|
| 153 | - $connection = \OCP\Server::get(Connection::class); |
|
| 154 | - return function (PropFind $propFind, INode $node) use ( |
|
| 155 | - $callBack, |
|
| 156 | - $pluginName, |
|
| 157 | - $eventName, |
|
| 158 | - $connection, |
|
| 159 | - ): bool { |
|
| 160 | - $queriesBefore = $connection->getStats()['executed']; |
|
| 161 | - $result = $callBack($propFind, $node); |
|
| 162 | - $queriesAfter = $connection->getStats()['executed']; |
|
| 163 | - $this->trackPluginQueries( |
|
| 164 | - $pluginName, |
|
| 165 | - $eventName, |
|
| 166 | - $queriesAfter - $queriesBefore, |
|
| 167 | - $propFind->getDepth() |
|
| 168 | - ); |
|
| 169 | - |
|
| 170 | - // many callbacks don't care about returning a bool |
|
| 171 | - return $result ?? true; |
|
| 172 | - }; |
|
| 173 | - } |
|
| 174 | - |
|
| 175 | - /** |
|
| 176 | - * Tracks the queries executed by a specific plugin. |
|
| 177 | - */ |
|
| 178 | - private function trackPluginQueries( |
|
| 179 | - string $pluginName, |
|
| 180 | - string $eventName, |
|
| 181 | - int $queriesExecuted, |
|
| 182 | - int $depth, |
|
| 183 | - ): void { |
|
| 184 | - // report only nodes which cause queries to the DB |
|
| 185 | - if ($queriesExecuted === 0) { |
|
| 186 | - return; |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - $this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] |
|
| 190 | - = ($this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] ?? 0) + 1; |
|
| 191 | - |
|
| 192 | - $this->pluginQueries[$eventName][$depth][$pluginName]['queries'] |
|
| 193 | - = ($this->pluginQueries[$eventName][$depth][$pluginName]['queries'] ?? 0) + $queriesExecuted; |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - /** |
|
| 197 | - * |
|
| 198 | - * @return void |
|
| 199 | - */ |
|
| 200 | - public function start() { |
|
| 201 | - try { |
|
| 202 | - // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an |
|
| 203 | - // origin, we must make sure we send back HTTP/1.0 if this was |
|
| 204 | - // requested. |
|
| 205 | - // This is mainly because nginx doesn't support Chunked Transfer |
|
| 206 | - // Encoding, and this forces the webserver SabreDAV is running on, |
|
| 207 | - // to buffer entire responses to calculate Content-Length. |
|
| 208 | - $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); |
|
| 209 | - |
|
| 210 | - // Setting the base url |
|
| 211 | - $this->httpRequest->setBaseUrl($this->getBaseUri()); |
|
| 212 | - $this->invokeMethod($this->httpRequest, $this->httpResponse); |
|
| 213 | - } catch (\Throwable $e) { |
|
| 214 | - try { |
|
| 215 | - $this->emit('exception', [$e]); |
|
| 216 | - } catch (\Exception) { |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - if ($e instanceof TypeError) { |
|
| 220 | - /* |
|
| 26 | + /** @var CachingTree $tree */ |
|
| 27 | + |
|
| 28 | + /** |
|
| 29 | + * Tracks queries done by plugins. |
|
| 30 | + * @var array<string, array<int, array<string, array{nodes:int, |
|
| 31 | + * queries:int}>>> The keys represent: event name, depth and plugin name |
|
| 32 | + */ |
|
| 33 | + private array $pluginQueries = []; |
|
| 34 | + |
|
| 35 | + public bool $debugEnabled = false; |
|
| 36 | + |
|
| 37 | + /** |
|
| 38 | + * @var array<string, array<int, callable>> |
|
| 39 | + */ |
|
| 40 | + private array $originalListeners = []; |
|
| 41 | + |
|
| 42 | + /** |
|
| 43 | + * @var array<string, array<int, callable>> |
|
| 44 | + */ |
|
| 45 | + private array $wrappedListeners = []; |
|
| 46 | + |
|
| 47 | + /** |
|
| 48 | + * @see \Sabre\DAV\Server |
|
| 49 | + */ |
|
| 50 | + public function __construct($treeOrNode = null) { |
|
| 51 | + parent::__construct($treeOrNode); |
|
| 52 | + self::$exposeVersion = false; |
|
| 53 | + $this->enablePropfindDepthInfinity = true; |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + #[Override] |
|
| 57 | + public function once( |
|
| 58 | + string $eventName, |
|
| 59 | + callable $callBack, |
|
| 60 | + int $priority = 100, |
|
| 61 | + ): void { |
|
| 62 | + $this->debugEnabled ? $this->monitorPropfindQueries( |
|
| 63 | + parent::once(...), |
|
| 64 | + ...\func_get_args(), |
|
| 65 | + ) : parent::once(...\func_get_args()); |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + #[Override] |
|
| 69 | + public function on( |
|
| 70 | + string $eventName, |
|
| 71 | + callable $callBack, |
|
| 72 | + int $priority = 100, |
|
| 73 | + ): void { |
|
| 74 | + $this->debugEnabled ? $this->monitorPropfindQueries( |
|
| 75 | + parent::on(...), |
|
| 76 | + ...\func_get_args(), |
|
| 77 | + ) : parent::on(...\func_get_args()); |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + /** |
|
| 81 | + * Wraps the handler $callBack into a query-monitoring function and calls |
|
| 82 | + * $parentFn to register it. |
|
| 83 | + */ |
|
| 84 | + private function monitorPropfindQueries( |
|
| 85 | + callable $parentFn, |
|
| 86 | + string $eventName, |
|
| 87 | + callable $callBack, |
|
| 88 | + int $priority = 100, |
|
| 89 | + ): void { |
|
| 90 | + $pluginName = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['class'] ?? 'unknown'; |
|
| 91 | + // The NotifyPlugin needs to be excluded as it emits the |
|
| 92 | + // `preloadCollection` event, which causes many plugins run queries. |
|
| 93 | + /** @psalm-suppress TypeDoesNotContainType */ |
|
| 94 | + if ($pluginName === PropFindPreloadNotifyPlugin::class || ($eventName !== 'propFind' |
|
| 95 | + && $eventName !== 'preloadCollection')) { |
|
| 96 | + $parentFn($eventName, $callBack, $priority); |
|
| 97 | + return; |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + $wrappedCallback |
|
| 101 | + = $this->getMonitoredCallback($callBack, $pluginName, $eventName); |
|
| 102 | + $this->originalListeners[$eventName][] = $callBack; |
|
| 103 | + $this->wrappedListeners[$eventName][] = $wrappedCallback; |
|
| 104 | + |
|
| 105 | + $parentFn($eventName, $wrappedCallback, $priority); |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + public function removeListener( |
|
| 109 | + string $eventName, |
|
| 110 | + callable $listener, |
|
| 111 | + ): bool { |
|
| 112 | + $listenerIndex = null; |
|
| 113 | + if (isset($this->wrappedListeners[$eventName], $this->originalListeners[$eventName])) { |
|
| 114 | + $key = array_search( |
|
| 115 | + $listener, |
|
| 116 | + $this->originalListeners[$eventName], |
|
| 117 | + true |
|
| 118 | + ); |
|
| 119 | + if ($key !== false) { |
|
| 120 | + $listenerIndex = $key; |
|
| 121 | + $listener = $this->wrappedListeners[$eventName][$listenerIndex]; |
|
| 122 | + } |
|
| 123 | + } |
|
| 124 | + $removed = parent::removeListener($eventName, $listener); |
|
| 125 | + |
|
| 126 | + if ($removed && $listenerIndex !== null) { |
|
| 127 | + unset($this->originalListeners[$eventName][$listenerIndex], $this->wrappedListeners[$eventName][$listenerIndex]); |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + return $removed; |
|
| 131 | + } |
|
| 132 | + |
|
| 133 | + public function removeAllListeners(?string $eventName = null): void { |
|
| 134 | + parent::removeAllListeners($eventName); |
|
| 135 | + |
|
| 136 | + if ($eventName === null) { |
|
| 137 | + $this->originalListeners = []; |
|
| 138 | + $this->wrappedListeners = []; |
|
| 139 | + } else { |
|
| 140 | + unset($this->wrappedListeners[$eventName], $this->originalListeners[$eventName]); |
|
| 141 | + } |
|
| 142 | + } |
|
| 143 | + |
|
| 144 | + /** |
|
| 145 | + * Returns a callable that wraps $callBack with code that monitors and |
|
| 146 | + * records queries per plugin. |
|
| 147 | + */ |
|
| 148 | + private function getMonitoredCallback( |
|
| 149 | + callable $callBack, |
|
| 150 | + string $pluginName, |
|
| 151 | + string $eventName, |
|
| 152 | + ): callable { |
|
| 153 | + $connection = \OCP\Server::get(Connection::class); |
|
| 154 | + return function (PropFind $propFind, INode $node) use ( |
|
| 155 | + $callBack, |
|
| 156 | + $pluginName, |
|
| 157 | + $eventName, |
|
| 158 | + $connection, |
|
| 159 | + ): bool { |
|
| 160 | + $queriesBefore = $connection->getStats()['executed']; |
|
| 161 | + $result = $callBack($propFind, $node); |
|
| 162 | + $queriesAfter = $connection->getStats()['executed']; |
|
| 163 | + $this->trackPluginQueries( |
|
| 164 | + $pluginName, |
|
| 165 | + $eventName, |
|
| 166 | + $queriesAfter - $queriesBefore, |
|
| 167 | + $propFind->getDepth() |
|
| 168 | + ); |
|
| 169 | + |
|
| 170 | + // many callbacks don't care about returning a bool |
|
| 171 | + return $result ?? true; |
|
| 172 | + }; |
|
| 173 | + } |
|
| 174 | + |
|
| 175 | + /** |
|
| 176 | + * Tracks the queries executed by a specific plugin. |
|
| 177 | + */ |
|
| 178 | + private function trackPluginQueries( |
|
| 179 | + string $pluginName, |
|
| 180 | + string $eventName, |
|
| 181 | + int $queriesExecuted, |
|
| 182 | + int $depth, |
|
| 183 | + ): void { |
|
| 184 | + // report only nodes which cause queries to the DB |
|
| 185 | + if ($queriesExecuted === 0) { |
|
| 186 | + return; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + $this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] |
|
| 190 | + = ($this->pluginQueries[$eventName][$depth][$pluginName]['nodes'] ?? 0) + 1; |
|
| 191 | + |
|
| 192 | + $this->pluginQueries[$eventName][$depth][$pluginName]['queries'] |
|
| 193 | + = ($this->pluginQueries[$eventName][$depth][$pluginName]['queries'] ?? 0) + $queriesExecuted; |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + /** |
|
| 197 | + * |
|
| 198 | + * @return void |
|
| 199 | + */ |
|
| 200 | + public function start() { |
|
| 201 | + try { |
|
| 202 | + // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an |
|
| 203 | + // origin, we must make sure we send back HTTP/1.0 if this was |
|
| 204 | + // requested. |
|
| 205 | + // This is mainly because nginx doesn't support Chunked Transfer |
|
| 206 | + // Encoding, and this forces the webserver SabreDAV is running on, |
|
| 207 | + // to buffer entire responses to calculate Content-Length. |
|
| 208 | + $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); |
|
| 209 | + |
|
| 210 | + // Setting the base url |
|
| 211 | + $this->httpRequest->setBaseUrl($this->getBaseUri()); |
|
| 212 | + $this->invokeMethod($this->httpRequest, $this->httpResponse); |
|
| 213 | + } catch (\Throwable $e) { |
|
| 214 | + try { |
|
| 215 | + $this->emit('exception', [$e]); |
|
| 216 | + } catch (\Exception) { |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + if ($e instanceof TypeError) { |
|
| 220 | + /* |
|
| 221 | 221 | * The TypeError includes the file path where the error occurred, |
| 222 | 222 | * potentially revealing the installation directory. |
| 223 | 223 | */ |
| 224 | - $e = new TypeError('A type error occurred. For more details, please refer to the logs, which provide additional context about the type error.'); |
|
| 225 | - } |
|
| 226 | - |
|
| 227 | - $DOM = new \DOMDocument('1.0', 'utf-8'); |
|
| 228 | - $DOM->formatOutput = true; |
|
| 229 | - |
|
| 230 | - $error = $DOM->createElementNS('DAV:', 'd:error'); |
|
| 231 | - $error->setAttribute('xmlns:s', self::NS_SABREDAV); |
|
| 232 | - $DOM->appendChild($error); |
|
| 233 | - |
|
| 234 | - $h = function ($v) { |
|
| 235 | - return htmlspecialchars((string)$v, ENT_NOQUOTES, 'UTF-8'); |
|
| 236 | - }; |
|
| 237 | - |
|
| 238 | - if (self::$exposeVersion) { |
|
| 239 | - $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION))); |
|
| 240 | - } |
|
| 241 | - |
|
| 242 | - $error->appendChild($DOM->createElement('s:exception', $h(get_class($e)))); |
|
| 243 | - $error->appendChild($DOM->createElement('s:message', $h($e->getMessage()))); |
|
| 244 | - if ($this->debugExceptions) { |
|
| 245 | - $error->appendChild($DOM->createElement('s:file', $h($e->getFile()))); |
|
| 246 | - $error->appendChild($DOM->createElement('s:line', $h($e->getLine()))); |
|
| 247 | - $error->appendChild($DOM->createElement('s:code', $h($e->getCode()))); |
|
| 248 | - $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString()))); |
|
| 249 | - } |
|
| 250 | - |
|
| 251 | - if ($this->debugExceptions) { |
|
| 252 | - $previous = $e; |
|
| 253 | - while ($previous = $previous->getPrevious()) { |
|
| 254 | - $xPrevious = $DOM->createElement('s:previous-exception'); |
|
| 255 | - $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous)))); |
|
| 256 | - $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage()))); |
|
| 257 | - $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile()))); |
|
| 258 | - $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine()))); |
|
| 259 | - $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode()))); |
|
| 260 | - $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString()))); |
|
| 261 | - $error->appendChild($xPrevious); |
|
| 262 | - } |
|
| 263 | - } |
|
| 264 | - |
|
| 265 | - if ($e instanceof Exception) { |
|
| 266 | - $httpCode = $e->getHTTPCode(); |
|
| 267 | - $e->serialize($this, $error); |
|
| 268 | - $headers = $e->getHTTPHeaders($this); |
|
| 269 | - } else { |
|
| 270 | - $httpCode = 500; |
|
| 271 | - $headers = []; |
|
| 272 | - } |
|
| 273 | - $headers['Content-Type'] = 'application/xml; charset=utf-8'; |
|
| 274 | - |
|
| 275 | - $this->httpResponse->setStatus($httpCode); |
|
| 276 | - $this->httpResponse->setHeaders($headers); |
|
| 277 | - $this->httpResponse->setBody($DOM->saveXML()); |
|
| 278 | - $this->sapi->sendResponse($this->httpResponse); |
|
| 279 | - } |
|
| 280 | - } |
|
| 281 | - |
|
| 282 | - /** |
|
| 283 | - * Returns queries executed by registered plugins. |
|
| 284 | - * @return array<string, array<int, array<string, array{nodes:int, |
|
| 285 | - * queries:int}>>> The keys represent: event name, depth and plugin name |
|
| 286 | - */ |
|
| 287 | - public function getPluginQueries(): array { |
|
| 288 | - return $this->pluginQueries; |
|
| 289 | - } |
|
| 224 | + $e = new TypeError('A type error occurred. For more details, please refer to the logs, which provide additional context about the type error.'); |
|
| 225 | + } |
|
| 226 | + |
|
| 227 | + $DOM = new \DOMDocument('1.0', 'utf-8'); |
|
| 228 | + $DOM->formatOutput = true; |
|
| 229 | + |
|
| 230 | + $error = $DOM->createElementNS('DAV:', 'd:error'); |
|
| 231 | + $error->setAttribute('xmlns:s', self::NS_SABREDAV); |
|
| 232 | + $DOM->appendChild($error); |
|
| 233 | + |
|
| 234 | + $h = function ($v) { |
|
| 235 | + return htmlspecialchars((string)$v, ENT_NOQUOTES, 'UTF-8'); |
|
| 236 | + }; |
|
| 237 | + |
|
| 238 | + if (self::$exposeVersion) { |
|
| 239 | + $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION))); |
|
| 240 | + } |
|
| 241 | + |
|
| 242 | + $error->appendChild($DOM->createElement('s:exception', $h(get_class($e)))); |
|
| 243 | + $error->appendChild($DOM->createElement('s:message', $h($e->getMessage()))); |
|
| 244 | + if ($this->debugExceptions) { |
|
| 245 | + $error->appendChild($DOM->createElement('s:file', $h($e->getFile()))); |
|
| 246 | + $error->appendChild($DOM->createElement('s:line', $h($e->getLine()))); |
|
| 247 | + $error->appendChild($DOM->createElement('s:code', $h($e->getCode()))); |
|
| 248 | + $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString()))); |
|
| 249 | + } |
|
| 250 | + |
|
| 251 | + if ($this->debugExceptions) { |
|
| 252 | + $previous = $e; |
|
| 253 | + while ($previous = $previous->getPrevious()) { |
|
| 254 | + $xPrevious = $DOM->createElement('s:previous-exception'); |
|
| 255 | + $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous)))); |
|
| 256 | + $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage()))); |
|
| 257 | + $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile()))); |
|
| 258 | + $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine()))); |
|
| 259 | + $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode()))); |
|
| 260 | + $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString()))); |
|
| 261 | + $error->appendChild($xPrevious); |
|
| 262 | + } |
|
| 263 | + } |
|
| 264 | + |
|
| 265 | + if ($e instanceof Exception) { |
|
| 266 | + $httpCode = $e->getHTTPCode(); |
|
| 267 | + $e->serialize($this, $error); |
|
| 268 | + $headers = $e->getHTTPHeaders($this); |
|
| 269 | + } else { |
|
| 270 | + $httpCode = 500; |
|
| 271 | + $headers = []; |
|
| 272 | + } |
|
| 273 | + $headers['Content-Type'] = 'application/xml; charset=utf-8'; |
|
| 274 | + |
|
| 275 | + $this->httpResponse->setStatus($httpCode); |
|
| 276 | + $this->httpResponse->setHeaders($headers); |
|
| 277 | + $this->httpResponse->setBody($DOM->saveXML()); |
|
| 278 | + $this->sapi->sendResponse($this->httpResponse); |
|
| 279 | + } |
|
| 280 | + } |
|
| 281 | + |
|
| 282 | + /** |
|
| 283 | + * Returns queries executed by registered plugins. |
|
| 284 | + * @return array<string, array<int, array<string, array{nodes:int, |
|
| 285 | + * queries:int}>>> The keys represent: event name, depth and plugin name |
|
| 286 | + */ |
|
| 287 | + public function getPluginQueries(): array { |
|
| 288 | + return $this->pluginQueries; |
|
| 289 | + } |
|
| 290 | 290 | } |
@@ -151,7 +151,7 @@ discard block |
||
| 151 | 151 | string $eventName, |
| 152 | 152 | ): callable { |
| 153 | 153 | $connection = \OCP\Server::get(Connection::class); |
| 154 | - return function (PropFind $propFind, INode $node) use ( |
|
| 154 | + return function(PropFind $propFind, INode $node) use ( |
|
| 155 | 155 | $callBack, |
| 156 | 156 | $pluginName, |
| 157 | 157 | $eventName, |
@@ -231,8 +231,8 @@ discard block |
||
| 231 | 231 | $error->setAttribute('xmlns:s', self::NS_SABREDAV); |
| 232 | 232 | $DOM->appendChild($error); |
| 233 | 233 | |
| 234 | - $h = function ($v) { |
|
| 235 | - return htmlspecialchars((string)$v, ENT_NOQUOTES, 'UTF-8'); |
|
| 234 | + $h = function($v) { |
|
| 235 | + return htmlspecialchars((string) $v, ENT_NOQUOTES, 'UTF-8'); |
|
| 236 | 236 | }; |
| 237 | 237 | |
| 238 | 238 | if (self::$exposeVersion) { |