@@ -43,541 +43,541 @@ |
||
43 | 43 | use OCP\Files\Search\ISearchQuery; |
44 | 44 | |
45 | 45 | class Folder extends Node implements \OCP\Files\Folder { |
46 | - /** |
|
47 | - * Creates a Folder that represents a non-existing path |
|
48 | - * |
|
49 | - * @param string $path path |
|
50 | - * @return string non-existing node class |
|
51 | - */ |
|
52 | - protected function createNonExistingNode($path) { |
|
53 | - return new NonExistingFolder($this->root, $this->view, $path); |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * @param string $path path relative to the folder |
|
58 | - * @return string |
|
59 | - * @throws \OCP\Files\NotPermittedException |
|
60 | - */ |
|
61 | - public function getFullPath($path) { |
|
62 | - if (!$this->isValidPath($path)) { |
|
63 | - throw new NotPermittedException('Invalid path'); |
|
64 | - } |
|
65 | - return $this->path . $this->normalizePath($path); |
|
66 | - } |
|
67 | - |
|
68 | - /** |
|
69 | - * @param string $path |
|
70 | - * @return string |
|
71 | - */ |
|
72 | - public function getRelativePath($path) { |
|
73 | - if ($this->path === '' or $this->path === '/') { |
|
74 | - return $this->normalizePath($path); |
|
75 | - } |
|
76 | - if ($path === $this->path) { |
|
77 | - return '/'; |
|
78 | - } elseif (strpos($path, $this->path . '/') !== 0) { |
|
79 | - return null; |
|
80 | - } else { |
|
81 | - $path = substr($path, strlen($this->path)); |
|
82 | - return $this->normalizePath($path); |
|
83 | - } |
|
84 | - } |
|
85 | - |
|
86 | - /** |
|
87 | - * check if a node is a (grand-)child of the folder |
|
88 | - * |
|
89 | - * @param \OC\Files\Node\Node $node |
|
90 | - * @return bool |
|
91 | - */ |
|
92 | - public function isSubNode($node) { |
|
93 | - return strpos($node->getPath(), $this->path . '/') === 0; |
|
94 | - } |
|
95 | - |
|
96 | - /** |
|
97 | - * get the content of this directory |
|
98 | - * |
|
99 | - * @throws \OCP\Files\NotFoundException |
|
100 | - * @return Node[] |
|
101 | - */ |
|
102 | - public function getDirectoryListing() { |
|
103 | - $folderContent = $this->view->getDirectoryContent($this->path); |
|
104 | - |
|
105 | - return array_map(function (FileInfo $info) { |
|
106 | - if ($info->getMimetype() === 'httpd/unix-directory') { |
|
107 | - return new Folder($this->root, $this->view, $info->getPath(), $info); |
|
108 | - } else { |
|
109 | - return new File($this->root, $this->view, $info->getPath(), $info); |
|
110 | - } |
|
111 | - }, $folderContent); |
|
112 | - } |
|
113 | - |
|
114 | - /** |
|
115 | - * @param string $path |
|
116 | - * @param FileInfo $info |
|
117 | - * @return File|Folder |
|
118 | - */ |
|
119 | - protected function createNode($path, FileInfo $info = null) { |
|
120 | - if (is_null($info)) { |
|
121 | - $isDir = $this->view->is_dir($path); |
|
122 | - } else { |
|
123 | - $isDir = $info->getType() === FileInfo::TYPE_FOLDER; |
|
124 | - } |
|
125 | - if ($isDir) { |
|
126 | - return new Folder($this->root, $this->view, $path, $info); |
|
127 | - } else { |
|
128 | - return new File($this->root, $this->view, $path, $info); |
|
129 | - } |
|
130 | - } |
|
131 | - |
|
132 | - /** |
|
133 | - * Get the node at $path |
|
134 | - * |
|
135 | - * @param string $path |
|
136 | - * @return \OC\Files\Node\Node |
|
137 | - * @throws \OCP\Files\NotFoundException |
|
138 | - */ |
|
139 | - public function get($path) { |
|
140 | - return $this->root->get($this->getFullPath($path)); |
|
141 | - } |
|
142 | - |
|
143 | - /** |
|
144 | - * @param string $path |
|
145 | - * @return bool |
|
146 | - */ |
|
147 | - public function nodeExists($path) { |
|
148 | - try { |
|
149 | - $this->get($path); |
|
150 | - return true; |
|
151 | - } catch (NotFoundException $e) { |
|
152 | - return false; |
|
153 | - } |
|
154 | - } |
|
155 | - |
|
156 | - /** |
|
157 | - * @param string $path |
|
158 | - * @return \OC\Files\Node\Folder |
|
159 | - * @throws \OCP\Files\NotPermittedException |
|
160 | - */ |
|
161 | - public function newFolder($path) { |
|
162 | - if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { |
|
163 | - $fullPath = $this->getFullPath($path); |
|
164 | - $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); |
|
165 | - $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); |
|
166 | - if (!$this->view->mkdir($fullPath)) { |
|
167 | - throw new NotPermittedException('Could not create folder'); |
|
168 | - } |
|
169 | - $node = new Folder($this->root, $this->view, $fullPath); |
|
170 | - $this->sendHooks(['postWrite', 'postCreate'], [$node]); |
|
171 | - return $node; |
|
172 | - } else { |
|
173 | - throw new NotPermittedException('No create permission for folder'); |
|
174 | - } |
|
175 | - } |
|
176 | - |
|
177 | - /** |
|
178 | - * @param string $path |
|
179 | - * @param string | resource | null $content |
|
180 | - * @return \OC\Files\Node\File |
|
181 | - * @throws \OCP\Files\NotPermittedException |
|
182 | - */ |
|
183 | - public function newFile($path, $content = null) { |
|
184 | - if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { |
|
185 | - $fullPath = $this->getFullPath($path); |
|
186 | - $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); |
|
187 | - $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); |
|
188 | - if ($content !== null) { |
|
189 | - $result = $this->view->file_put_contents($fullPath, $content); |
|
190 | - } else { |
|
191 | - $result = $this->view->touch($fullPath); |
|
192 | - } |
|
193 | - if ($result === false) { |
|
194 | - throw new NotPermittedException('Could not create path'); |
|
195 | - } |
|
196 | - $node = new File($this->root, $this->view, $fullPath); |
|
197 | - $this->sendHooks(['postWrite', 'postCreate'], [$node]); |
|
198 | - return $node; |
|
199 | - } |
|
200 | - throw new NotPermittedException('No create permission for path'); |
|
201 | - } |
|
202 | - |
|
203 | - /** |
|
204 | - * search for files with the name matching $query |
|
205 | - * |
|
206 | - * @param string|ISearchQuery $query |
|
207 | - * @return \OC\Files\Node\Node[] |
|
208 | - */ |
|
209 | - public function search($query) { |
|
210 | - if (is_string($query)) { |
|
211 | - return $this->searchCommon('search', ['%' . $query . '%']); |
|
212 | - } else { |
|
213 | - return $this->searchCommon('searchQuery', [$query]); |
|
214 | - } |
|
215 | - } |
|
216 | - |
|
217 | - /** |
|
218 | - * search for files by mimetype |
|
219 | - * |
|
220 | - * @param string $mimetype |
|
221 | - * @return Node[] |
|
222 | - */ |
|
223 | - public function searchByMime($mimetype) { |
|
224 | - return $this->searchCommon('searchByMime', [$mimetype]); |
|
225 | - } |
|
226 | - |
|
227 | - /** |
|
228 | - * search for files by tag |
|
229 | - * |
|
230 | - * @param string|int $tag name or tag id |
|
231 | - * @param string $userId owner of the tags |
|
232 | - * @return Node[] |
|
233 | - */ |
|
234 | - public function searchByTag($tag, $userId) { |
|
235 | - return $this->searchCommon('searchByTag', [$tag, $userId]); |
|
236 | - } |
|
237 | - |
|
238 | - /** |
|
239 | - * @param string $method cache method |
|
240 | - * @param array $args call args |
|
241 | - * @return \OC\Files\Node\Node[] |
|
242 | - */ |
|
243 | - private function searchCommon($method, $args) { |
|
244 | - $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false; |
|
245 | - if ($limitToHome && count(explode('/', $this->path)) !== 3) { |
|
246 | - throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); |
|
247 | - } |
|
248 | - |
|
249 | - $files = []; |
|
250 | - $rootLength = strlen($this->path); |
|
251 | - $mount = $this->root->getMount($this->path); |
|
252 | - $storage = $mount->getStorage(); |
|
253 | - $internalPath = $mount->getInternalPath($this->path); |
|
254 | - $internalPath = rtrim($internalPath, '/'); |
|
255 | - if ($internalPath !== '') { |
|
256 | - $internalPath = $internalPath . '/'; |
|
257 | - } |
|
258 | - $internalRootLength = strlen($internalPath); |
|
259 | - |
|
260 | - $cache = $storage->getCache(''); |
|
261 | - |
|
262 | - $results = call_user_func_array([$cache, $method], $args); |
|
263 | - foreach ($results as $result) { |
|
264 | - if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) { |
|
265 | - $result['internalPath'] = $result['path']; |
|
266 | - $result['path'] = substr($result['path'], $internalRootLength); |
|
267 | - $result['storage'] = $storage; |
|
268 | - $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); |
|
269 | - } |
|
270 | - } |
|
271 | - |
|
272 | - if (!$limitToHome) { |
|
273 | - $mounts = $this->root->getMountsIn($this->path); |
|
274 | - foreach ($mounts as $mount) { |
|
275 | - $storage = $mount->getStorage(); |
|
276 | - if ($storage) { |
|
277 | - $cache = $storage->getCache(''); |
|
278 | - |
|
279 | - $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); |
|
280 | - $results = call_user_func_array([$cache, $method], $args); |
|
281 | - foreach ($results as $result) { |
|
282 | - $result['internalPath'] = $result['path']; |
|
283 | - $result['path'] = $relativeMountPoint . $result['path']; |
|
284 | - $result['storage'] = $storage; |
|
285 | - $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, |
|
286 | - $result['internalPath'], $result, $mount); |
|
287 | - } |
|
288 | - } |
|
289 | - } |
|
290 | - } |
|
291 | - |
|
292 | - return array_map(function (FileInfo $file) { |
|
293 | - return $this->createNode($file->getPath(), $file); |
|
294 | - }, $files); |
|
295 | - } |
|
296 | - |
|
297 | - /** |
|
298 | - * @param int $id |
|
299 | - * @return \OC\Files\Node\Node[] |
|
300 | - */ |
|
301 | - public function getById($id) { |
|
302 | - $mountCache = $this->root->getUserMountCache(); |
|
303 | - if (strpos($this->getPath(), '/', 1) > 0) { |
|
304 | - [, $user] = explode('/', $this->getPath()); |
|
305 | - } else { |
|
306 | - $user = null; |
|
307 | - } |
|
308 | - $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user); |
|
309 | - $mounts = $this->root->getMountsIn($this->path); |
|
310 | - $mounts[] = $this->root->getMount($this->path); |
|
311 | - /** @var IMountPoint[] $folderMounts */ |
|
312 | - $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) { |
|
313 | - return $mountPoint->getMountPoint(); |
|
314 | - }, $mounts), $mounts); |
|
315 | - |
|
316 | - /** @var ICachedMountInfo[] $mountsContainingFile */ |
|
317 | - $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) { |
|
318 | - return isset($folderMounts[$cachedMountInfo->getMountPoint()]); |
|
319 | - })); |
|
320 | - |
|
321 | - if (count($mountsContainingFile) === 0) { |
|
322 | - if ($user === $this->getAppDataDirectoryName()) { |
|
323 | - return $this->getByIdInRootMount((int) $id); |
|
324 | - } |
|
325 | - return []; |
|
326 | - } |
|
327 | - |
|
328 | - $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) { |
|
329 | - $mount = $folderMounts[$cachedMountInfo->getMountPoint()]; |
|
330 | - $cacheEntry = $mount->getStorage()->getCache()->get((int)$id); |
|
331 | - if (!$cacheEntry) { |
|
332 | - return null; |
|
333 | - } |
|
334 | - |
|
335 | - // cache jails will hide the "true" internal path |
|
336 | - $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/'); |
|
337 | - $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath())); |
|
338 | - $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); |
|
339 | - $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/'); |
|
340 | - return $this->root->createNode($absolutePath, new \OC\Files\FileInfo( |
|
341 | - $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, |
|
342 | - \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) |
|
343 | - )); |
|
344 | - }, $mountsContainingFile); |
|
345 | - |
|
346 | - $nodes = array_filter($nodes); |
|
347 | - |
|
348 | - return array_filter($nodes, function (Node $node) { |
|
349 | - return $this->getRelativePath($node->getPath()); |
|
350 | - }); |
|
351 | - } |
|
352 | - |
|
353 | - protected function getAppDataDirectoryName(): string { |
|
354 | - $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid'); |
|
355 | - return 'appdata_' . $instanceId; |
|
356 | - } |
|
357 | - |
|
358 | - /** |
|
359 | - * In case the path we are currently in is inside the appdata_* folder, |
|
360 | - * the original getById method does not work, because it can only look inside |
|
361 | - * the user's mount points. But the user has no mount point for the root storage. |
|
362 | - * |
|
363 | - * So in that case we directly check the mount of the root if it contains |
|
364 | - * the id. If it does we check if the path is inside the path we are working |
|
365 | - * in. |
|
366 | - * |
|
367 | - * @param int $id |
|
368 | - * @return array |
|
369 | - */ |
|
370 | - protected function getByIdInRootMount(int $id): array { |
|
371 | - $mount = $this->root->getMount(''); |
|
372 | - $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id); |
|
373 | - if (!$cacheEntry) { |
|
374 | - return []; |
|
375 | - } |
|
376 | - |
|
377 | - $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/'); |
|
378 | - $currentPath = rtrim($this->path, '/') . '/'; |
|
379 | - |
|
380 | - if (strpos($absolutePath, $currentPath) !== 0) { |
|
381 | - return []; |
|
382 | - } |
|
383 | - |
|
384 | - return [$this->root->createNode( |
|
385 | - $absolutePath, new \OC\Files\FileInfo( |
|
386 | - $absolutePath, |
|
387 | - $mount->getStorage(), |
|
388 | - $cacheEntry->getPath(), |
|
389 | - $cacheEntry, |
|
390 | - $mount |
|
391 | - ))]; |
|
392 | - } |
|
393 | - |
|
394 | - public function getFreeSpace() { |
|
395 | - return $this->view->free_space($this->path); |
|
396 | - } |
|
397 | - |
|
398 | - public function delete() { |
|
399 | - if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { |
|
400 | - $this->sendHooks(['preDelete']); |
|
401 | - $fileInfo = $this->getFileInfo(); |
|
402 | - $this->view->rmdir($this->path); |
|
403 | - $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); |
|
404 | - $this->sendHooks(['postDelete'], [$nonExisting]); |
|
405 | - $this->exists = false; |
|
406 | - } else { |
|
407 | - throw new NotPermittedException('No delete permission for path'); |
|
408 | - } |
|
409 | - } |
|
410 | - |
|
411 | - /** |
|
412 | - * Add a suffix to the name in case the file exists |
|
413 | - * |
|
414 | - * @param string $name |
|
415 | - * @return string |
|
416 | - * @throws NotPermittedException |
|
417 | - */ |
|
418 | - public function getNonExistingName($name) { |
|
419 | - $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view); |
|
420 | - return trim($this->getRelativePath($uniqueName), '/'); |
|
421 | - } |
|
422 | - |
|
423 | - /** |
|
424 | - * @param int $limit |
|
425 | - * @param int $offset |
|
426 | - * @return \OCP\Files\Node[] |
|
427 | - */ |
|
428 | - public function getRecent($limit, $offset = 0) { |
|
429 | - $mimetypeLoader = \OC::$server->getMimeTypeLoader(); |
|
430 | - $mounts = $this->root->getMountsIn($this->path); |
|
431 | - $mounts[] = $this->getMountPoint(); |
|
432 | - |
|
433 | - $mounts = array_filter($mounts, function (IMountPoint $mount) { |
|
434 | - return $mount->getStorage(); |
|
435 | - }); |
|
436 | - $storageIds = array_map(function (IMountPoint $mount) { |
|
437 | - return $mount->getStorage()->getCache()->getNumericStorageId(); |
|
438 | - }, $mounts); |
|
439 | - /** @var IMountPoint[] $mountMap */ |
|
440 | - $mountMap = array_combine($storageIds, $mounts); |
|
441 | - $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER); |
|
442 | - |
|
443 | - /* |
|
46 | + /** |
|
47 | + * Creates a Folder that represents a non-existing path |
|
48 | + * |
|
49 | + * @param string $path path |
|
50 | + * @return string non-existing node class |
|
51 | + */ |
|
52 | + protected function createNonExistingNode($path) { |
|
53 | + return new NonExistingFolder($this->root, $this->view, $path); |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * @param string $path path relative to the folder |
|
58 | + * @return string |
|
59 | + * @throws \OCP\Files\NotPermittedException |
|
60 | + */ |
|
61 | + public function getFullPath($path) { |
|
62 | + if (!$this->isValidPath($path)) { |
|
63 | + throw new NotPermittedException('Invalid path'); |
|
64 | + } |
|
65 | + return $this->path . $this->normalizePath($path); |
|
66 | + } |
|
67 | + |
|
68 | + /** |
|
69 | + * @param string $path |
|
70 | + * @return string |
|
71 | + */ |
|
72 | + public function getRelativePath($path) { |
|
73 | + if ($this->path === '' or $this->path === '/') { |
|
74 | + return $this->normalizePath($path); |
|
75 | + } |
|
76 | + if ($path === $this->path) { |
|
77 | + return '/'; |
|
78 | + } elseif (strpos($path, $this->path . '/') !== 0) { |
|
79 | + return null; |
|
80 | + } else { |
|
81 | + $path = substr($path, strlen($this->path)); |
|
82 | + return $this->normalizePath($path); |
|
83 | + } |
|
84 | + } |
|
85 | + |
|
86 | + /** |
|
87 | + * check if a node is a (grand-)child of the folder |
|
88 | + * |
|
89 | + * @param \OC\Files\Node\Node $node |
|
90 | + * @return bool |
|
91 | + */ |
|
92 | + public function isSubNode($node) { |
|
93 | + return strpos($node->getPath(), $this->path . '/') === 0; |
|
94 | + } |
|
95 | + |
|
96 | + /** |
|
97 | + * get the content of this directory |
|
98 | + * |
|
99 | + * @throws \OCP\Files\NotFoundException |
|
100 | + * @return Node[] |
|
101 | + */ |
|
102 | + public function getDirectoryListing() { |
|
103 | + $folderContent = $this->view->getDirectoryContent($this->path); |
|
104 | + |
|
105 | + return array_map(function (FileInfo $info) { |
|
106 | + if ($info->getMimetype() === 'httpd/unix-directory') { |
|
107 | + return new Folder($this->root, $this->view, $info->getPath(), $info); |
|
108 | + } else { |
|
109 | + return new File($this->root, $this->view, $info->getPath(), $info); |
|
110 | + } |
|
111 | + }, $folderContent); |
|
112 | + } |
|
113 | + |
|
114 | + /** |
|
115 | + * @param string $path |
|
116 | + * @param FileInfo $info |
|
117 | + * @return File|Folder |
|
118 | + */ |
|
119 | + protected function createNode($path, FileInfo $info = null) { |
|
120 | + if (is_null($info)) { |
|
121 | + $isDir = $this->view->is_dir($path); |
|
122 | + } else { |
|
123 | + $isDir = $info->getType() === FileInfo::TYPE_FOLDER; |
|
124 | + } |
|
125 | + if ($isDir) { |
|
126 | + return new Folder($this->root, $this->view, $path, $info); |
|
127 | + } else { |
|
128 | + return new File($this->root, $this->view, $path, $info); |
|
129 | + } |
|
130 | + } |
|
131 | + |
|
132 | + /** |
|
133 | + * Get the node at $path |
|
134 | + * |
|
135 | + * @param string $path |
|
136 | + * @return \OC\Files\Node\Node |
|
137 | + * @throws \OCP\Files\NotFoundException |
|
138 | + */ |
|
139 | + public function get($path) { |
|
140 | + return $this->root->get($this->getFullPath($path)); |
|
141 | + } |
|
142 | + |
|
143 | + /** |
|
144 | + * @param string $path |
|
145 | + * @return bool |
|
146 | + */ |
|
147 | + public function nodeExists($path) { |
|
148 | + try { |
|
149 | + $this->get($path); |
|
150 | + return true; |
|
151 | + } catch (NotFoundException $e) { |
|
152 | + return false; |
|
153 | + } |
|
154 | + } |
|
155 | + |
|
156 | + /** |
|
157 | + * @param string $path |
|
158 | + * @return \OC\Files\Node\Folder |
|
159 | + * @throws \OCP\Files\NotPermittedException |
|
160 | + */ |
|
161 | + public function newFolder($path) { |
|
162 | + if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { |
|
163 | + $fullPath = $this->getFullPath($path); |
|
164 | + $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); |
|
165 | + $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); |
|
166 | + if (!$this->view->mkdir($fullPath)) { |
|
167 | + throw new NotPermittedException('Could not create folder'); |
|
168 | + } |
|
169 | + $node = new Folder($this->root, $this->view, $fullPath); |
|
170 | + $this->sendHooks(['postWrite', 'postCreate'], [$node]); |
|
171 | + return $node; |
|
172 | + } else { |
|
173 | + throw new NotPermittedException('No create permission for folder'); |
|
174 | + } |
|
175 | + } |
|
176 | + |
|
177 | + /** |
|
178 | + * @param string $path |
|
179 | + * @param string | resource | null $content |
|
180 | + * @return \OC\Files\Node\File |
|
181 | + * @throws \OCP\Files\NotPermittedException |
|
182 | + */ |
|
183 | + public function newFile($path, $content = null) { |
|
184 | + if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { |
|
185 | + $fullPath = $this->getFullPath($path); |
|
186 | + $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); |
|
187 | + $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]); |
|
188 | + if ($content !== null) { |
|
189 | + $result = $this->view->file_put_contents($fullPath, $content); |
|
190 | + } else { |
|
191 | + $result = $this->view->touch($fullPath); |
|
192 | + } |
|
193 | + if ($result === false) { |
|
194 | + throw new NotPermittedException('Could not create path'); |
|
195 | + } |
|
196 | + $node = new File($this->root, $this->view, $fullPath); |
|
197 | + $this->sendHooks(['postWrite', 'postCreate'], [$node]); |
|
198 | + return $node; |
|
199 | + } |
|
200 | + throw new NotPermittedException('No create permission for path'); |
|
201 | + } |
|
202 | + |
|
203 | + /** |
|
204 | + * search for files with the name matching $query |
|
205 | + * |
|
206 | + * @param string|ISearchQuery $query |
|
207 | + * @return \OC\Files\Node\Node[] |
|
208 | + */ |
|
209 | + public function search($query) { |
|
210 | + if (is_string($query)) { |
|
211 | + return $this->searchCommon('search', ['%' . $query . '%']); |
|
212 | + } else { |
|
213 | + return $this->searchCommon('searchQuery', [$query]); |
|
214 | + } |
|
215 | + } |
|
216 | + |
|
217 | + /** |
|
218 | + * search for files by mimetype |
|
219 | + * |
|
220 | + * @param string $mimetype |
|
221 | + * @return Node[] |
|
222 | + */ |
|
223 | + public function searchByMime($mimetype) { |
|
224 | + return $this->searchCommon('searchByMime', [$mimetype]); |
|
225 | + } |
|
226 | + |
|
227 | + /** |
|
228 | + * search for files by tag |
|
229 | + * |
|
230 | + * @param string|int $tag name or tag id |
|
231 | + * @param string $userId owner of the tags |
|
232 | + * @return Node[] |
|
233 | + */ |
|
234 | + public function searchByTag($tag, $userId) { |
|
235 | + return $this->searchCommon('searchByTag', [$tag, $userId]); |
|
236 | + } |
|
237 | + |
|
238 | + /** |
|
239 | + * @param string $method cache method |
|
240 | + * @param array $args call args |
|
241 | + * @return \OC\Files\Node\Node[] |
|
242 | + */ |
|
243 | + private function searchCommon($method, $args) { |
|
244 | + $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false; |
|
245 | + if ($limitToHome && count(explode('/', $this->path)) !== 3) { |
|
246 | + throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); |
|
247 | + } |
|
248 | + |
|
249 | + $files = []; |
|
250 | + $rootLength = strlen($this->path); |
|
251 | + $mount = $this->root->getMount($this->path); |
|
252 | + $storage = $mount->getStorage(); |
|
253 | + $internalPath = $mount->getInternalPath($this->path); |
|
254 | + $internalPath = rtrim($internalPath, '/'); |
|
255 | + if ($internalPath !== '') { |
|
256 | + $internalPath = $internalPath . '/'; |
|
257 | + } |
|
258 | + $internalRootLength = strlen($internalPath); |
|
259 | + |
|
260 | + $cache = $storage->getCache(''); |
|
261 | + |
|
262 | + $results = call_user_func_array([$cache, $method], $args); |
|
263 | + foreach ($results as $result) { |
|
264 | + if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) { |
|
265 | + $result['internalPath'] = $result['path']; |
|
266 | + $result['path'] = substr($result['path'], $internalRootLength); |
|
267 | + $result['storage'] = $storage; |
|
268 | + $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); |
|
269 | + } |
|
270 | + } |
|
271 | + |
|
272 | + if (!$limitToHome) { |
|
273 | + $mounts = $this->root->getMountsIn($this->path); |
|
274 | + foreach ($mounts as $mount) { |
|
275 | + $storage = $mount->getStorage(); |
|
276 | + if ($storage) { |
|
277 | + $cache = $storage->getCache(''); |
|
278 | + |
|
279 | + $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); |
|
280 | + $results = call_user_func_array([$cache, $method], $args); |
|
281 | + foreach ($results as $result) { |
|
282 | + $result['internalPath'] = $result['path']; |
|
283 | + $result['path'] = $relativeMountPoint . $result['path']; |
|
284 | + $result['storage'] = $storage; |
|
285 | + $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, |
|
286 | + $result['internalPath'], $result, $mount); |
|
287 | + } |
|
288 | + } |
|
289 | + } |
|
290 | + } |
|
291 | + |
|
292 | + return array_map(function (FileInfo $file) { |
|
293 | + return $this->createNode($file->getPath(), $file); |
|
294 | + }, $files); |
|
295 | + } |
|
296 | + |
|
297 | + /** |
|
298 | + * @param int $id |
|
299 | + * @return \OC\Files\Node\Node[] |
|
300 | + */ |
|
301 | + public function getById($id) { |
|
302 | + $mountCache = $this->root->getUserMountCache(); |
|
303 | + if (strpos($this->getPath(), '/', 1) > 0) { |
|
304 | + [, $user] = explode('/', $this->getPath()); |
|
305 | + } else { |
|
306 | + $user = null; |
|
307 | + } |
|
308 | + $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user); |
|
309 | + $mounts = $this->root->getMountsIn($this->path); |
|
310 | + $mounts[] = $this->root->getMount($this->path); |
|
311 | + /** @var IMountPoint[] $folderMounts */ |
|
312 | + $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) { |
|
313 | + return $mountPoint->getMountPoint(); |
|
314 | + }, $mounts), $mounts); |
|
315 | + |
|
316 | + /** @var ICachedMountInfo[] $mountsContainingFile */ |
|
317 | + $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) { |
|
318 | + return isset($folderMounts[$cachedMountInfo->getMountPoint()]); |
|
319 | + })); |
|
320 | + |
|
321 | + if (count($mountsContainingFile) === 0) { |
|
322 | + if ($user === $this->getAppDataDirectoryName()) { |
|
323 | + return $this->getByIdInRootMount((int) $id); |
|
324 | + } |
|
325 | + return []; |
|
326 | + } |
|
327 | + |
|
328 | + $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) { |
|
329 | + $mount = $folderMounts[$cachedMountInfo->getMountPoint()]; |
|
330 | + $cacheEntry = $mount->getStorage()->getCache()->get((int)$id); |
|
331 | + if (!$cacheEntry) { |
|
332 | + return null; |
|
333 | + } |
|
334 | + |
|
335 | + // cache jails will hide the "true" internal path |
|
336 | + $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/'); |
|
337 | + $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath())); |
|
338 | + $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); |
|
339 | + $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/'); |
|
340 | + return $this->root->createNode($absolutePath, new \OC\Files\FileInfo( |
|
341 | + $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, |
|
342 | + \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) |
|
343 | + )); |
|
344 | + }, $mountsContainingFile); |
|
345 | + |
|
346 | + $nodes = array_filter($nodes); |
|
347 | + |
|
348 | + return array_filter($nodes, function (Node $node) { |
|
349 | + return $this->getRelativePath($node->getPath()); |
|
350 | + }); |
|
351 | + } |
|
352 | + |
|
353 | + protected function getAppDataDirectoryName(): string { |
|
354 | + $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid'); |
|
355 | + return 'appdata_' . $instanceId; |
|
356 | + } |
|
357 | + |
|
358 | + /** |
|
359 | + * In case the path we are currently in is inside the appdata_* folder, |
|
360 | + * the original getById method does not work, because it can only look inside |
|
361 | + * the user's mount points. But the user has no mount point for the root storage. |
|
362 | + * |
|
363 | + * So in that case we directly check the mount of the root if it contains |
|
364 | + * the id. If it does we check if the path is inside the path we are working |
|
365 | + * in. |
|
366 | + * |
|
367 | + * @param int $id |
|
368 | + * @return array |
|
369 | + */ |
|
370 | + protected function getByIdInRootMount(int $id): array { |
|
371 | + $mount = $this->root->getMount(''); |
|
372 | + $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id); |
|
373 | + if (!$cacheEntry) { |
|
374 | + return []; |
|
375 | + } |
|
376 | + |
|
377 | + $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/'); |
|
378 | + $currentPath = rtrim($this->path, '/') . '/'; |
|
379 | + |
|
380 | + if (strpos($absolutePath, $currentPath) !== 0) { |
|
381 | + return []; |
|
382 | + } |
|
383 | + |
|
384 | + return [$this->root->createNode( |
|
385 | + $absolutePath, new \OC\Files\FileInfo( |
|
386 | + $absolutePath, |
|
387 | + $mount->getStorage(), |
|
388 | + $cacheEntry->getPath(), |
|
389 | + $cacheEntry, |
|
390 | + $mount |
|
391 | + ))]; |
|
392 | + } |
|
393 | + |
|
394 | + public function getFreeSpace() { |
|
395 | + return $this->view->free_space($this->path); |
|
396 | + } |
|
397 | + |
|
398 | + public function delete() { |
|
399 | + if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { |
|
400 | + $this->sendHooks(['preDelete']); |
|
401 | + $fileInfo = $this->getFileInfo(); |
|
402 | + $this->view->rmdir($this->path); |
|
403 | + $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); |
|
404 | + $this->sendHooks(['postDelete'], [$nonExisting]); |
|
405 | + $this->exists = false; |
|
406 | + } else { |
|
407 | + throw new NotPermittedException('No delete permission for path'); |
|
408 | + } |
|
409 | + } |
|
410 | + |
|
411 | + /** |
|
412 | + * Add a suffix to the name in case the file exists |
|
413 | + * |
|
414 | + * @param string $name |
|
415 | + * @return string |
|
416 | + * @throws NotPermittedException |
|
417 | + */ |
|
418 | + public function getNonExistingName($name) { |
|
419 | + $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view); |
|
420 | + return trim($this->getRelativePath($uniqueName), '/'); |
|
421 | + } |
|
422 | + |
|
423 | + /** |
|
424 | + * @param int $limit |
|
425 | + * @param int $offset |
|
426 | + * @return \OCP\Files\Node[] |
|
427 | + */ |
|
428 | + public function getRecent($limit, $offset = 0) { |
|
429 | + $mimetypeLoader = \OC::$server->getMimeTypeLoader(); |
|
430 | + $mounts = $this->root->getMountsIn($this->path); |
|
431 | + $mounts[] = $this->getMountPoint(); |
|
432 | + |
|
433 | + $mounts = array_filter($mounts, function (IMountPoint $mount) { |
|
434 | + return $mount->getStorage(); |
|
435 | + }); |
|
436 | + $storageIds = array_map(function (IMountPoint $mount) { |
|
437 | + return $mount->getStorage()->getCache()->getNumericStorageId(); |
|
438 | + }, $mounts); |
|
439 | + /** @var IMountPoint[] $mountMap */ |
|
440 | + $mountMap = array_combine($storageIds, $mounts); |
|
441 | + $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER); |
|
442 | + |
|
443 | + /* |
|
444 | 444 | * Construct an array of the storage id with their prefix path |
445 | 445 | * This helps us to filter in the final query |
446 | 446 | */ |
447 | - $filters = array_map(function (IMountPoint $mount) { |
|
448 | - $storage = $mount->getStorage(); |
|
449 | - |
|
450 | - $storageId = $storage->getCache()->getNumericStorageId(); |
|
451 | - $prefix = ''; |
|
452 | - |
|
453 | - if ($storage->instanceOfStorage(Jail::class)) { |
|
454 | - $prefix = $storage->getUnJailedPath(''); |
|
455 | - } |
|
456 | - |
|
457 | - return [ |
|
458 | - 'storageId' => $storageId, |
|
459 | - 'pathPrefix' => $prefix, |
|
460 | - ]; |
|
461 | - }, $mounts); |
|
462 | - |
|
463 | - // Search in batches of 500 entries |
|
464 | - $searchLimit = 500; |
|
465 | - $results = []; |
|
466 | - $searchResultCount = 0; |
|
467 | - $count = 0; |
|
468 | - do { |
|
469 | - $searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters); |
|
470 | - |
|
471 | - // Exit condition if there are no more results |
|
472 | - if (count($searchResult) === 0) { |
|
473 | - break; |
|
474 | - } |
|
475 | - |
|
476 | - $searchResultCount += count($searchResult); |
|
477 | - |
|
478 | - $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader); |
|
479 | - |
|
480 | - foreach ($parseResult as $result) { |
|
481 | - $results[] = $result; |
|
482 | - } |
|
483 | - |
|
484 | - $offset += $searchLimit; |
|
485 | - $count++; |
|
486 | - } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5)); |
|
487 | - |
|
488 | - return array_slice($results, 0, $limit); |
|
489 | - } |
|
490 | - |
|
491 | - private function recentSearch($limit, $offset, $folderMimetype, $filters) { |
|
492 | - $dbconn = \OC::$server->getDatabaseConnection(); |
|
493 | - $builder = $dbconn->getQueryBuilder(); |
|
494 | - $query = $builder |
|
495 | - ->select('f.*') |
|
496 | - ->from('filecache', 'f'); |
|
497 | - |
|
498 | - /* |
|
447 | + $filters = array_map(function (IMountPoint $mount) { |
|
448 | + $storage = $mount->getStorage(); |
|
449 | + |
|
450 | + $storageId = $storage->getCache()->getNumericStorageId(); |
|
451 | + $prefix = ''; |
|
452 | + |
|
453 | + if ($storage->instanceOfStorage(Jail::class)) { |
|
454 | + $prefix = $storage->getUnJailedPath(''); |
|
455 | + } |
|
456 | + |
|
457 | + return [ |
|
458 | + 'storageId' => $storageId, |
|
459 | + 'pathPrefix' => $prefix, |
|
460 | + ]; |
|
461 | + }, $mounts); |
|
462 | + |
|
463 | + // Search in batches of 500 entries |
|
464 | + $searchLimit = 500; |
|
465 | + $results = []; |
|
466 | + $searchResultCount = 0; |
|
467 | + $count = 0; |
|
468 | + do { |
|
469 | + $searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters); |
|
470 | + |
|
471 | + // Exit condition if there are no more results |
|
472 | + if (count($searchResult) === 0) { |
|
473 | + break; |
|
474 | + } |
|
475 | + |
|
476 | + $searchResultCount += count($searchResult); |
|
477 | + |
|
478 | + $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader); |
|
479 | + |
|
480 | + foreach ($parseResult as $result) { |
|
481 | + $results[] = $result; |
|
482 | + } |
|
483 | + |
|
484 | + $offset += $searchLimit; |
|
485 | + $count++; |
|
486 | + } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5)); |
|
487 | + |
|
488 | + return array_slice($results, 0, $limit); |
|
489 | + } |
|
490 | + |
|
491 | + private function recentSearch($limit, $offset, $folderMimetype, $filters) { |
|
492 | + $dbconn = \OC::$server->getDatabaseConnection(); |
|
493 | + $builder = $dbconn->getQueryBuilder(); |
|
494 | + $query = $builder |
|
495 | + ->select('f.*') |
|
496 | + ->from('filecache', 'f'); |
|
497 | + |
|
498 | + /* |
|
499 | 499 | * Here is where we construct the filtering. |
500 | 500 | * Note that this is expensive filtering as it is a lot of like queries. |
501 | 501 | * However the alternative is we do this filtering and parsing later in php with the risk of looping endlessly |
502 | 502 | */ |
503 | - $storageFilters = $builder->expr()->orX(); |
|
504 | - foreach ($filters as $filter) { |
|
505 | - $storageFilter = $builder->expr()->andX( |
|
506 | - $builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId'])) |
|
507 | - ); |
|
508 | - |
|
509 | - if ($filter['pathPrefix'] !== '') { |
|
510 | - $storageFilter->add( |
|
511 | - $builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%')) |
|
512 | - ); |
|
513 | - } |
|
514 | - |
|
515 | - $storageFilters->add($storageFilter); |
|
516 | - } |
|
517 | - |
|
518 | - $query->andWhere($storageFilters); |
|
519 | - |
|
520 | - $query->andWhere($builder->expr()->orX( |
|
521 | - // handle non empty folders separate |
|
522 | - $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)), |
|
523 | - $builder->expr()->eq('f.size', new Literal(0)) |
|
524 | - )) |
|
525 | - ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%'))) |
|
526 | - ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%'))) |
|
527 | - ->orderBy('f.mtime', 'DESC') |
|
528 | - ->setMaxResults($limit) |
|
529 | - ->setFirstResult($offset); |
|
530 | - |
|
531 | - $result = $query->execute(); |
|
532 | - $rows = $result->fetchAll(); |
|
533 | - $result->closeCursor(); |
|
534 | - |
|
535 | - return $rows; |
|
536 | - } |
|
537 | - |
|
538 | - private function recentParse($result, $mountMap, $mimetypeLoader) { |
|
539 | - $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) { |
|
540 | - $mount = $mountMap[$entry['storage']]; |
|
541 | - $entry['internalPath'] = $entry['path']; |
|
542 | - $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']); |
|
543 | - $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']); |
|
544 | - $path = $this->getAbsolutePath($mount, $entry['path']); |
|
545 | - if (is_null($path)) { |
|
546 | - return null; |
|
547 | - } |
|
548 | - $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount); |
|
549 | - return $this->root->createNode($fileInfo->getPath(), $fileInfo); |
|
550 | - }, $result)); |
|
551 | - |
|
552 | - return array_values(array_filter($files, function (Node $node) { |
|
553 | - $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId()); |
|
554 | - if (!$cacheEntry) { |
|
555 | - return false; |
|
556 | - } |
|
557 | - $relative = $this->getRelativePath($node->getPath()); |
|
558 | - return $relative !== null && $relative !== '/' |
|
559 | - && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ; |
|
560 | - })); |
|
561 | - } |
|
562 | - |
|
563 | - private function getAbsolutePath(IMountPoint $mount, $path) { |
|
564 | - $storage = $mount->getStorage(); |
|
565 | - if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) { |
|
566 | - if ($storage->instanceOfStorage(SharedStorage::class)) { |
|
567 | - $storage->getSourceStorage(); |
|
568 | - } |
|
569 | - /** @var \OC\Files\Storage\Wrapper\Jail $storage */ |
|
570 | - $jailRoot = $storage->getUnjailedPath(''); |
|
571 | - $rootLength = strlen($jailRoot) + 1; |
|
572 | - if ($path === $jailRoot) { |
|
573 | - return $mount->getMountPoint(); |
|
574 | - } elseif (substr($path, 0, $rootLength) === $jailRoot . '/') { |
|
575 | - return $mount->getMountPoint() . substr($path, $rootLength); |
|
576 | - } else { |
|
577 | - return null; |
|
578 | - } |
|
579 | - } else { |
|
580 | - return $mount->getMountPoint() . $path; |
|
581 | - } |
|
582 | - } |
|
503 | + $storageFilters = $builder->expr()->orX(); |
|
504 | + foreach ($filters as $filter) { |
|
505 | + $storageFilter = $builder->expr()->andX( |
|
506 | + $builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId'])) |
|
507 | + ); |
|
508 | + |
|
509 | + if ($filter['pathPrefix'] !== '') { |
|
510 | + $storageFilter->add( |
|
511 | + $builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%')) |
|
512 | + ); |
|
513 | + } |
|
514 | + |
|
515 | + $storageFilters->add($storageFilter); |
|
516 | + } |
|
517 | + |
|
518 | + $query->andWhere($storageFilters); |
|
519 | + |
|
520 | + $query->andWhere($builder->expr()->orX( |
|
521 | + // handle non empty folders separate |
|
522 | + $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)), |
|
523 | + $builder->expr()->eq('f.size', new Literal(0)) |
|
524 | + )) |
|
525 | + ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%'))) |
|
526 | + ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%'))) |
|
527 | + ->orderBy('f.mtime', 'DESC') |
|
528 | + ->setMaxResults($limit) |
|
529 | + ->setFirstResult($offset); |
|
530 | + |
|
531 | + $result = $query->execute(); |
|
532 | + $rows = $result->fetchAll(); |
|
533 | + $result->closeCursor(); |
|
534 | + |
|
535 | + return $rows; |
|
536 | + } |
|
537 | + |
|
538 | + private function recentParse($result, $mountMap, $mimetypeLoader) { |
|
539 | + $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) { |
|
540 | + $mount = $mountMap[$entry['storage']]; |
|
541 | + $entry['internalPath'] = $entry['path']; |
|
542 | + $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']); |
|
543 | + $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']); |
|
544 | + $path = $this->getAbsolutePath($mount, $entry['path']); |
|
545 | + if (is_null($path)) { |
|
546 | + return null; |
|
547 | + } |
|
548 | + $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount); |
|
549 | + return $this->root->createNode($fileInfo->getPath(), $fileInfo); |
|
550 | + }, $result)); |
|
551 | + |
|
552 | + return array_values(array_filter($files, function (Node $node) { |
|
553 | + $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId()); |
|
554 | + if (!$cacheEntry) { |
|
555 | + return false; |
|
556 | + } |
|
557 | + $relative = $this->getRelativePath($node->getPath()); |
|
558 | + return $relative !== null && $relative !== '/' |
|
559 | + && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ; |
|
560 | + })); |
|
561 | + } |
|
562 | + |
|
563 | + private function getAbsolutePath(IMountPoint $mount, $path) { |
|
564 | + $storage = $mount->getStorage(); |
|
565 | + if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) { |
|
566 | + if ($storage->instanceOfStorage(SharedStorage::class)) { |
|
567 | + $storage->getSourceStorage(); |
|
568 | + } |
|
569 | + /** @var \OC\Files\Storage\Wrapper\Jail $storage */ |
|
570 | + $jailRoot = $storage->getUnjailedPath(''); |
|
571 | + $rootLength = strlen($jailRoot) + 1; |
|
572 | + if ($path === $jailRoot) { |
|
573 | + return $mount->getMountPoint(); |
|
574 | + } elseif (substr($path, 0, $rootLength) === $jailRoot . '/') { |
|
575 | + return $mount->getMountPoint() . substr($path, $rootLength); |
|
576 | + } else { |
|
577 | + return null; |
|
578 | + } |
|
579 | + } else { |
|
580 | + return $mount->getMountPoint() . $path; |
|
581 | + } |
|
582 | + } |
|
583 | 583 | } |
@@ -41,428 +41,428 @@ |
||
41 | 41 | |
42 | 42 | // FIXME: this class really should be abstract |
43 | 43 | class Node implements \OCP\Files\Node { |
44 | - /** |
|
45 | - * @var \OC\Files\View $view |
|
46 | - */ |
|
47 | - protected $view; |
|
48 | - |
|
49 | - /** |
|
50 | - * @var \OC\Files\Node\Root $root |
|
51 | - */ |
|
52 | - protected $root; |
|
53 | - |
|
54 | - /** |
|
55 | - * @var string $path |
|
56 | - */ |
|
57 | - protected $path; |
|
58 | - |
|
59 | - /** |
|
60 | - * @var \OCP\Files\FileInfo |
|
61 | - */ |
|
62 | - protected $fileInfo; |
|
63 | - |
|
64 | - /** |
|
65 | - * @param \OC\Files\View $view |
|
66 | - * @param \OCP\Files\IRootFolder $root |
|
67 | - * @param string $path |
|
68 | - * @param FileInfo $fileInfo |
|
69 | - */ |
|
70 | - public function __construct($root, $view, $path, $fileInfo = null) { |
|
71 | - $this->view = $view; |
|
72 | - $this->root = $root; |
|
73 | - $this->path = $path; |
|
74 | - $this->fileInfo = $fileInfo; |
|
75 | - } |
|
76 | - |
|
77 | - /** |
|
78 | - * Creates a Node of the same type that represents a non-existing path |
|
79 | - * |
|
80 | - * @param string $path path |
|
81 | - * @return string non-existing node class |
|
82 | - * @throws \Exception |
|
83 | - */ |
|
84 | - protected function createNonExistingNode($path) { |
|
85 | - throw new \Exception('Must be implemented by subclasses'); |
|
86 | - } |
|
87 | - |
|
88 | - /** |
|
89 | - * Returns the matching file info |
|
90 | - * |
|
91 | - * @return FileInfo |
|
92 | - * @throws InvalidPathException |
|
93 | - * @throws NotFoundException |
|
94 | - */ |
|
95 | - public function getFileInfo() { |
|
96 | - if (!Filesystem::isValidPath($this->path)) { |
|
97 | - throw new InvalidPathException(); |
|
98 | - } |
|
99 | - if (!$this->fileInfo) { |
|
100 | - $fileInfo = $this->view->getFileInfo($this->path); |
|
101 | - if ($fileInfo instanceof FileInfo) { |
|
102 | - $this->fileInfo = $fileInfo; |
|
103 | - } else { |
|
104 | - throw new NotFoundException(); |
|
105 | - } |
|
106 | - } |
|
107 | - return $this->fileInfo; |
|
108 | - } |
|
109 | - |
|
110 | - /** |
|
111 | - * @param string[] $hooks |
|
112 | - */ |
|
113 | - protected function sendHooks($hooks, array $args = null) { |
|
114 | - $args = !empty($args) ? $args : [$this]; |
|
115 | - $dispatcher = \OC::$server->getEventDispatcher(); |
|
116 | - foreach ($hooks as $hook) { |
|
117 | - $this->root->emit('\OC\Files', $hook, $args); |
|
118 | - $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); |
|
119 | - } |
|
120 | - } |
|
121 | - |
|
122 | - /** |
|
123 | - * @param int $permissions |
|
124 | - * @return bool |
|
125 | - * @throws InvalidPathException |
|
126 | - * @throws NotFoundException |
|
127 | - */ |
|
128 | - protected function checkPermissions($permissions) { |
|
129 | - return ($this->getPermissions() & $permissions) === $permissions; |
|
130 | - } |
|
131 | - |
|
132 | - public function delete() { |
|
133 | - } |
|
134 | - |
|
135 | - /** |
|
136 | - * @param int $mtime |
|
137 | - * @throws InvalidPathException |
|
138 | - * @throws NotFoundException |
|
139 | - * @throws NotPermittedException |
|
140 | - */ |
|
141 | - public function touch($mtime = null) { |
|
142 | - if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { |
|
143 | - $this->sendHooks(['preTouch']); |
|
144 | - $this->view->touch($this->path, $mtime); |
|
145 | - $this->sendHooks(['postTouch']); |
|
146 | - if ($this->fileInfo) { |
|
147 | - if (is_null($mtime)) { |
|
148 | - $mtime = time(); |
|
149 | - } |
|
150 | - $this->fileInfo['mtime'] = $mtime; |
|
151 | - } |
|
152 | - } else { |
|
153 | - throw new NotPermittedException(); |
|
154 | - } |
|
155 | - } |
|
156 | - |
|
157 | - /** |
|
158 | - * @return \OC\Files\Storage\Storage |
|
159 | - * @throws \OCP\Files\NotFoundException |
|
160 | - */ |
|
161 | - public function getStorage() { |
|
162 | - [$storage,] = $this->view->resolvePath($this->path); |
|
163 | - return $storage; |
|
164 | - } |
|
165 | - |
|
166 | - /** |
|
167 | - * @return string |
|
168 | - */ |
|
169 | - public function getPath() { |
|
170 | - return $this->path; |
|
171 | - } |
|
172 | - |
|
173 | - /** |
|
174 | - * @return string |
|
175 | - */ |
|
176 | - public function getInternalPath() { |
|
177 | - [, $internalPath] = $this->view->resolvePath($this->path); |
|
178 | - return $internalPath; |
|
179 | - } |
|
180 | - |
|
181 | - /** |
|
182 | - * @return int |
|
183 | - * @throws InvalidPathException |
|
184 | - * @throws NotFoundException |
|
185 | - */ |
|
186 | - public function getId() { |
|
187 | - return $this->getFileInfo()->getId(); |
|
188 | - } |
|
189 | - |
|
190 | - /** |
|
191 | - * @return array |
|
192 | - */ |
|
193 | - public function stat() { |
|
194 | - return $this->view->stat($this->path); |
|
195 | - } |
|
196 | - |
|
197 | - /** |
|
198 | - * @return int |
|
199 | - * @throws InvalidPathException |
|
200 | - * @throws NotFoundException |
|
201 | - */ |
|
202 | - public function getMTime() { |
|
203 | - return $this->getFileInfo()->getMTime(); |
|
204 | - } |
|
205 | - |
|
206 | - /** |
|
207 | - * @param bool $includeMounts |
|
208 | - * @return int |
|
209 | - * @throws InvalidPathException |
|
210 | - * @throws NotFoundException |
|
211 | - */ |
|
212 | - public function getSize($includeMounts = true) { |
|
213 | - return $this->getFileInfo()->getSize($includeMounts); |
|
214 | - } |
|
215 | - |
|
216 | - /** |
|
217 | - * @return string |
|
218 | - * @throws InvalidPathException |
|
219 | - * @throws NotFoundException |
|
220 | - */ |
|
221 | - public function getEtag() { |
|
222 | - return $this->getFileInfo()->getEtag(); |
|
223 | - } |
|
224 | - |
|
225 | - /** |
|
226 | - * @return int |
|
227 | - * @throws InvalidPathException |
|
228 | - * @throws NotFoundException |
|
229 | - */ |
|
230 | - public function getPermissions() { |
|
231 | - return $this->getFileInfo()->getPermissions(); |
|
232 | - } |
|
233 | - |
|
234 | - /** |
|
235 | - * @return bool |
|
236 | - * @throws InvalidPathException |
|
237 | - * @throws NotFoundException |
|
238 | - */ |
|
239 | - public function isReadable() { |
|
240 | - return $this->getFileInfo()->isReadable(); |
|
241 | - } |
|
242 | - |
|
243 | - /** |
|
244 | - * @return bool |
|
245 | - * @throws InvalidPathException |
|
246 | - * @throws NotFoundException |
|
247 | - */ |
|
248 | - public function isUpdateable() { |
|
249 | - return $this->getFileInfo()->isUpdateable(); |
|
250 | - } |
|
251 | - |
|
252 | - /** |
|
253 | - * @return bool |
|
254 | - * @throws InvalidPathException |
|
255 | - * @throws NotFoundException |
|
256 | - */ |
|
257 | - public function isDeletable() { |
|
258 | - return $this->getFileInfo()->isDeletable(); |
|
259 | - } |
|
260 | - |
|
261 | - /** |
|
262 | - * @return bool |
|
263 | - * @throws InvalidPathException |
|
264 | - * @throws NotFoundException |
|
265 | - */ |
|
266 | - public function isShareable() { |
|
267 | - return $this->getFileInfo()->isShareable(); |
|
268 | - } |
|
269 | - |
|
270 | - /** |
|
271 | - * @return bool |
|
272 | - * @throws InvalidPathException |
|
273 | - * @throws NotFoundException |
|
274 | - */ |
|
275 | - public function isCreatable() { |
|
276 | - return $this->getFileInfo()->isCreatable(); |
|
277 | - } |
|
278 | - |
|
279 | - /** |
|
280 | - * @return Node |
|
281 | - */ |
|
282 | - public function getParent() { |
|
283 | - $newPath = dirname($this->path); |
|
284 | - if ($newPath === '' || $newPath === '.' || $newPath === '/') { |
|
285 | - return $this->root; |
|
286 | - } |
|
287 | - return $this->root->get($newPath); |
|
288 | - } |
|
289 | - |
|
290 | - /** |
|
291 | - * @return string |
|
292 | - */ |
|
293 | - public function getName() { |
|
294 | - return basename($this->path); |
|
295 | - } |
|
296 | - |
|
297 | - /** |
|
298 | - * @param string $path |
|
299 | - * @return string |
|
300 | - */ |
|
301 | - protected function normalizePath($path) { |
|
302 | - if ($path === '' or $path === '/') { |
|
303 | - return '/'; |
|
304 | - } |
|
305 | - //no windows style slashes |
|
306 | - $path = str_replace('\\', '/', $path); |
|
307 | - //add leading slash |
|
308 | - if ($path[0] !== '/') { |
|
309 | - $path = '/' . $path; |
|
310 | - } |
|
311 | - //remove duplicate slashes |
|
312 | - while (strpos($path, '//') !== false) { |
|
313 | - $path = str_replace('//', '/', $path); |
|
314 | - } |
|
315 | - //remove trailing slash |
|
316 | - $path = rtrim($path, '/'); |
|
317 | - |
|
318 | - return $path; |
|
319 | - } |
|
320 | - |
|
321 | - /** |
|
322 | - * check if the requested path is valid |
|
323 | - * |
|
324 | - * @param string $path |
|
325 | - * @return bool |
|
326 | - */ |
|
327 | - public function isValidPath($path) { |
|
328 | - if (!$path || $path[0] !== '/') { |
|
329 | - $path = '/' . $path; |
|
330 | - } |
|
331 | - if (strstr($path, '/../') || strrchr($path, '/') === '/..') { |
|
332 | - return false; |
|
333 | - } |
|
334 | - return true; |
|
335 | - } |
|
336 | - |
|
337 | - public function isMounted() { |
|
338 | - return $this->getFileInfo()->isMounted(); |
|
339 | - } |
|
340 | - |
|
341 | - public function isShared() { |
|
342 | - return $this->getFileInfo()->isShared(); |
|
343 | - } |
|
344 | - |
|
345 | - public function getMimeType() { |
|
346 | - return $this->getFileInfo()->getMimetype(); |
|
347 | - } |
|
348 | - |
|
349 | - public function getMimePart() { |
|
350 | - return $this->getFileInfo()->getMimePart(); |
|
351 | - } |
|
352 | - |
|
353 | - public function getType() { |
|
354 | - return $this->getFileInfo()->getType(); |
|
355 | - } |
|
356 | - |
|
357 | - public function isEncrypted() { |
|
358 | - return $this->getFileInfo()->isEncrypted(); |
|
359 | - } |
|
360 | - |
|
361 | - public function getMountPoint() { |
|
362 | - return $this->getFileInfo()->getMountPoint(); |
|
363 | - } |
|
364 | - |
|
365 | - public function getOwner() { |
|
366 | - return $this->getFileInfo()->getOwner(); |
|
367 | - } |
|
368 | - |
|
369 | - public function getChecksum() { |
|
370 | - } |
|
371 | - |
|
372 | - public function getExtension(): string { |
|
373 | - return $this->getFileInfo()->getExtension(); |
|
374 | - } |
|
375 | - |
|
376 | - /** |
|
377 | - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE |
|
378 | - * @throws LockedException |
|
379 | - */ |
|
380 | - public function lock($type) { |
|
381 | - $this->view->lockFile($this->path, $type); |
|
382 | - } |
|
383 | - |
|
384 | - /** |
|
385 | - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE |
|
386 | - * @throws LockedException |
|
387 | - */ |
|
388 | - public function changeLock($type) { |
|
389 | - $this->view->changeLock($this->path, $type); |
|
390 | - } |
|
391 | - |
|
392 | - /** |
|
393 | - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE |
|
394 | - * @throws LockedException |
|
395 | - */ |
|
396 | - public function unlock($type) { |
|
397 | - $this->view->unlockFile($this->path, $type); |
|
398 | - } |
|
399 | - |
|
400 | - /** |
|
401 | - * @param string $targetPath |
|
402 | - * @return \OC\Files\Node\Node |
|
403 | - * @throws InvalidPathException |
|
404 | - * @throws NotFoundException |
|
405 | - * @throws NotPermittedException if copy not allowed or failed |
|
406 | - */ |
|
407 | - public function copy($targetPath) { |
|
408 | - $targetPath = $this->normalizePath($targetPath); |
|
409 | - $parent = $this->root->get(dirname($targetPath)); |
|
410 | - if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { |
|
411 | - $nonExisting = $this->createNonExistingNode($targetPath); |
|
412 | - $this->sendHooks(['preCopy'], [$this, $nonExisting]); |
|
413 | - $this->sendHooks(['preWrite'], [$nonExisting]); |
|
414 | - if (!$this->view->copy($this->path, $targetPath)) { |
|
415 | - throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath); |
|
416 | - } |
|
417 | - $targetNode = $this->root->get($targetPath); |
|
418 | - $this->sendHooks(['postCopy'], [$this, $targetNode]); |
|
419 | - $this->sendHooks(['postWrite'], [$targetNode]); |
|
420 | - return $targetNode; |
|
421 | - } else { |
|
422 | - throw new NotPermittedException('No permission to copy to path ' . $targetPath); |
|
423 | - } |
|
424 | - } |
|
425 | - |
|
426 | - /** |
|
427 | - * @param string $targetPath |
|
428 | - * @return \OC\Files\Node\Node |
|
429 | - * @throws InvalidPathException |
|
430 | - * @throws NotFoundException |
|
431 | - * @throws NotPermittedException if move not allowed or failed |
|
432 | - * @throws LockedException |
|
433 | - */ |
|
434 | - public function move($targetPath) { |
|
435 | - $targetPath = $this->normalizePath($targetPath); |
|
436 | - $parent = $this->root->get(dirname($targetPath)); |
|
437 | - if ( |
|
438 | - $parent instanceof Folder and |
|
439 | - $this->isValidPath($targetPath) and |
|
440 | - ( |
|
441 | - $parent->isCreatable() || |
|
442 | - ($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount) |
|
443 | - ) |
|
444 | - ) { |
|
445 | - $nonExisting = $this->createNonExistingNode($targetPath); |
|
446 | - $this->sendHooks(['preRename'], [$this, $nonExisting]); |
|
447 | - $this->sendHooks(['preWrite'], [$nonExisting]); |
|
448 | - if (!$this->view->rename($this->path, $targetPath)) { |
|
449 | - throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); |
|
450 | - } |
|
451 | - $targetNode = $this->root->get($targetPath); |
|
452 | - $this->sendHooks(['postRename'], [$this, $targetNode]); |
|
453 | - $this->sendHooks(['postWrite'], [$targetNode]); |
|
454 | - $this->path = $targetPath; |
|
455 | - return $targetNode; |
|
456 | - } else { |
|
457 | - throw new NotPermittedException('No permission to move to path ' . $targetPath); |
|
458 | - } |
|
459 | - } |
|
460 | - |
|
461 | - public function getCreationTime(): int { |
|
462 | - return $this->getFileInfo()->getCreationTime(); |
|
463 | - } |
|
464 | - |
|
465 | - public function getUploadTime(): int { |
|
466 | - return $this->getFileInfo()->getUploadTime(); |
|
467 | - } |
|
44 | + /** |
|
45 | + * @var \OC\Files\View $view |
|
46 | + */ |
|
47 | + protected $view; |
|
48 | + |
|
49 | + /** |
|
50 | + * @var \OC\Files\Node\Root $root |
|
51 | + */ |
|
52 | + protected $root; |
|
53 | + |
|
54 | + /** |
|
55 | + * @var string $path |
|
56 | + */ |
|
57 | + protected $path; |
|
58 | + |
|
59 | + /** |
|
60 | + * @var \OCP\Files\FileInfo |
|
61 | + */ |
|
62 | + protected $fileInfo; |
|
63 | + |
|
64 | + /** |
|
65 | + * @param \OC\Files\View $view |
|
66 | + * @param \OCP\Files\IRootFolder $root |
|
67 | + * @param string $path |
|
68 | + * @param FileInfo $fileInfo |
|
69 | + */ |
|
70 | + public function __construct($root, $view, $path, $fileInfo = null) { |
|
71 | + $this->view = $view; |
|
72 | + $this->root = $root; |
|
73 | + $this->path = $path; |
|
74 | + $this->fileInfo = $fileInfo; |
|
75 | + } |
|
76 | + |
|
77 | + /** |
|
78 | + * Creates a Node of the same type that represents a non-existing path |
|
79 | + * |
|
80 | + * @param string $path path |
|
81 | + * @return string non-existing node class |
|
82 | + * @throws \Exception |
|
83 | + */ |
|
84 | + protected function createNonExistingNode($path) { |
|
85 | + throw new \Exception('Must be implemented by subclasses'); |
|
86 | + } |
|
87 | + |
|
88 | + /** |
|
89 | + * Returns the matching file info |
|
90 | + * |
|
91 | + * @return FileInfo |
|
92 | + * @throws InvalidPathException |
|
93 | + * @throws NotFoundException |
|
94 | + */ |
|
95 | + public function getFileInfo() { |
|
96 | + if (!Filesystem::isValidPath($this->path)) { |
|
97 | + throw new InvalidPathException(); |
|
98 | + } |
|
99 | + if (!$this->fileInfo) { |
|
100 | + $fileInfo = $this->view->getFileInfo($this->path); |
|
101 | + if ($fileInfo instanceof FileInfo) { |
|
102 | + $this->fileInfo = $fileInfo; |
|
103 | + } else { |
|
104 | + throw new NotFoundException(); |
|
105 | + } |
|
106 | + } |
|
107 | + return $this->fileInfo; |
|
108 | + } |
|
109 | + |
|
110 | + /** |
|
111 | + * @param string[] $hooks |
|
112 | + */ |
|
113 | + protected function sendHooks($hooks, array $args = null) { |
|
114 | + $args = !empty($args) ? $args : [$this]; |
|
115 | + $dispatcher = \OC::$server->getEventDispatcher(); |
|
116 | + foreach ($hooks as $hook) { |
|
117 | + $this->root->emit('\OC\Files', $hook, $args); |
|
118 | + $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); |
|
119 | + } |
|
120 | + } |
|
121 | + |
|
122 | + /** |
|
123 | + * @param int $permissions |
|
124 | + * @return bool |
|
125 | + * @throws InvalidPathException |
|
126 | + * @throws NotFoundException |
|
127 | + */ |
|
128 | + protected function checkPermissions($permissions) { |
|
129 | + return ($this->getPermissions() & $permissions) === $permissions; |
|
130 | + } |
|
131 | + |
|
132 | + public function delete() { |
|
133 | + } |
|
134 | + |
|
135 | + /** |
|
136 | + * @param int $mtime |
|
137 | + * @throws InvalidPathException |
|
138 | + * @throws NotFoundException |
|
139 | + * @throws NotPermittedException |
|
140 | + */ |
|
141 | + public function touch($mtime = null) { |
|
142 | + if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { |
|
143 | + $this->sendHooks(['preTouch']); |
|
144 | + $this->view->touch($this->path, $mtime); |
|
145 | + $this->sendHooks(['postTouch']); |
|
146 | + if ($this->fileInfo) { |
|
147 | + if (is_null($mtime)) { |
|
148 | + $mtime = time(); |
|
149 | + } |
|
150 | + $this->fileInfo['mtime'] = $mtime; |
|
151 | + } |
|
152 | + } else { |
|
153 | + throw new NotPermittedException(); |
|
154 | + } |
|
155 | + } |
|
156 | + |
|
157 | + /** |
|
158 | + * @return \OC\Files\Storage\Storage |
|
159 | + * @throws \OCP\Files\NotFoundException |
|
160 | + */ |
|
161 | + public function getStorage() { |
|
162 | + [$storage,] = $this->view->resolvePath($this->path); |
|
163 | + return $storage; |
|
164 | + } |
|
165 | + |
|
166 | + /** |
|
167 | + * @return string |
|
168 | + */ |
|
169 | + public function getPath() { |
|
170 | + return $this->path; |
|
171 | + } |
|
172 | + |
|
173 | + /** |
|
174 | + * @return string |
|
175 | + */ |
|
176 | + public function getInternalPath() { |
|
177 | + [, $internalPath] = $this->view->resolvePath($this->path); |
|
178 | + return $internalPath; |
|
179 | + } |
|
180 | + |
|
181 | + /** |
|
182 | + * @return int |
|
183 | + * @throws InvalidPathException |
|
184 | + * @throws NotFoundException |
|
185 | + */ |
|
186 | + public function getId() { |
|
187 | + return $this->getFileInfo()->getId(); |
|
188 | + } |
|
189 | + |
|
190 | + /** |
|
191 | + * @return array |
|
192 | + */ |
|
193 | + public function stat() { |
|
194 | + return $this->view->stat($this->path); |
|
195 | + } |
|
196 | + |
|
197 | + /** |
|
198 | + * @return int |
|
199 | + * @throws InvalidPathException |
|
200 | + * @throws NotFoundException |
|
201 | + */ |
|
202 | + public function getMTime() { |
|
203 | + return $this->getFileInfo()->getMTime(); |
|
204 | + } |
|
205 | + |
|
206 | + /** |
|
207 | + * @param bool $includeMounts |
|
208 | + * @return int |
|
209 | + * @throws InvalidPathException |
|
210 | + * @throws NotFoundException |
|
211 | + */ |
|
212 | + public function getSize($includeMounts = true) { |
|
213 | + return $this->getFileInfo()->getSize($includeMounts); |
|
214 | + } |
|
215 | + |
|
216 | + /** |
|
217 | + * @return string |
|
218 | + * @throws InvalidPathException |
|
219 | + * @throws NotFoundException |
|
220 | + */ |
|
221 | + public function getEtag() { |
|
222 | + return $this->getFileInfo()->getEtag(); |
|
223 | + } |
|
224 | + |
|
225 | + /** |
|
226 | + * @return int |
|
227 | + * @throws InvalidPathException |
|
228 | + * @throws NotFoundException |
|
229 | + */ |
|
230 | + public function getPermissions() { |
|
231 | + return $this->getFileInfo()->getPermissions(); |
|
232 | + } |
|
233 | + |
|
234 | + /** |
|
235 | + * @return bool |
|
236 | + * @throws InvalidPathException |
|
237 | + * @throws NotFoundException |
|
238 | + */ |
|
239 | + public function isReadable() { |
|
240 | + return $this->getFileInfo()->isReadable(); |
|
241 | + } |
|
242 | + |
|
243 | + /** |
|
244 | + * @return bool |
|
245 | + * @throws InvalidPathException |
|
246 | + * @throws NotFoundException |
|
247 | + */ |
|
248 | + public function isUpdateable() { |
|
249 | + return $this->getFileInfo()->isUpdateable(); |
|
250 | + } |
|
251 | + |
|
252 | + /** |
|
253 | + * @return bool |
|
254 | + * @throws InvalidPathException |
|
255 | + * @throws NotFoundException |
|
256 | + */ |
|
257 | + public function isDeletable() { |
|
258 | + return $this->getFileInfo()->isDeletable(); |
|
259 | + } |
|
260 | + |
|
261 | + /** |
|
262 | + * @return bool |
|
263 | + * @throws InvalidPathException |
|
264 | + * @throws NotFoundException |
|
265 | + */ |
|
266 | + public function isShareable() { |
|
267 | + return $this->getFileInfo()->isShareable(); |
|
268 | + } |
|
269 | + |
|
270 | + /** |
|
271 | + * @return bool |
|
272 | + * @throws InvalidPathException |
|
273 | + * @throws NotFoundException |
|
274 | + */ |
|
275 | + public function isCreatable() { |
|
276 | + return $this->getFileInfo()->isCreatable(); |
|
277 | + } |
|
278 | + |
|
279 | + /** |
|
280 | + * @return Node |
|
281 | + */ |
|
282 | + public function getParent() { |
|
283 | + $newPath = dirname($this->path); |
|
284 | + if ($newPath === '' || $newPath === '.' || $newPath === '/') { |
|
285 | + return $this->root; |
|
286 | + } |
|
287 | + return $this->root->get($newPath); |
|
288 | + } |
|
289 | + |
|
290 | + /** |
|
291 | + * @return string |
|
292 | + */ |
|
293 | + public function getName() { |
|
294 | + return basename($this->path); |
|
295 | + } |
|
296 | + |
|
297 | + /** |
|
298 | + * @param string $path |
|
299 | + * @return string |
|
300 | + */ |
|
301 | + protected function normalizePath($path) { |
|
302 | + if ($path === '' or $path === '/') { |
|
303 | + return '/'; |
|
304 | + } |
|
305 | + //no windows style slashes |
|
306 | + $path = str_replace('\\', '/', $path); |
|
307 | + //add leading slash |
|
308 | + if ($path[0] !== '/') { |
|
309 | + $path = '/' . $path; |
|
310 | + } |
|
311 | + //remove duplicate slashes |
|
312 | + while (strpos($path, '//') !== false) { |
|
313 | + $path = str_replace('//', '/', $path); |
|
314 | + } |
|
315 | + //remove trailing slash |
|
316 | + $path = rtrim($path, '/'); |
|
317 | + |
|
318 | + return $path; |
|
319 | + } |
|
320 | + |
|
321 | + /** |
|
322 | + * check if the requested path is valid |
|
323 | + * |
|
324 | + * @param string $path |
|
325 | + * @return bool |
|
326 | + */ |
|
327 | + public function isValidPath($path) { |
|
328 | + if (!$path || $path[0] !== '/') { |
|
329 | + $path = '/' . $path; |
|
330 | + } |
|
331 | + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { |
|
332 | + return false; |
|
333 | + } |
|
334 | + return true; |
|
335 | + } |
|
336 | + |
|
337 | + public function isMounted() { |
|
338 | + return $this->getFileInfo()->isMounted(); |
|
339 | + } |
|
340 | + |
|
341 | + public function isShared() { |
|
342 | + return $this->getFileInfo()->isShared(); |
|
343 | + } |
|
344 | + |
|
345 | + public function getMimeType() { |
|
346 | + return $this->getFileInfo()->getMimetype(); |
|
347 | + } |
|
348 | + |
|
349 | + public function getMimePart() { |
|
350 | + return $this->getFileInfo()->getMimePart(); |
|
351 | + } |
|
352 | + |
|
353 | + public function getType() { |
|
354 | + return $this->getFileInfo()->getType(); |
|
355 | + } |
|
356 | + |
|
357 | + public function isEncrypted() { |
|
358 | + return $this->getFileInfo()->isEncrypted(); |
|
359 | + } |
|
360 | + |
|
361 | + public function getMountPoint() { |
|
362 | + return $this->getFileInfo()->getMountPoint(); |
|
363 | + } |
|
364 | + |
|
365 | + public function getOwner() { |
|
366 | + return $this->getFileInfo()->getOwner(); |
|
367 | + } |
|
368 | + |
|
369 | + public function getChecksum() { |
|
370 | + } |
|
371 | + |
|
372 | + public function getExtension(): string { |
|
373 | + return $this->getFileInfo()->getExtension(); |
|
374 | + } |
|
375 | + |
|
376 | + /** |
|
377 | + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE |
|
378 | + * @throws LockedException |
|
379 | + */ |
|
380 | + public function lock($type) { |
|
381 | + $this->view->lockFile($this->path, $type); |
|
382 | + } |
|
383 | + |
|
384 | + /** |
|
385 | + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE |
|
386 | + * @throws LockedException |
|
387 | + */ |
|
388 | + public function changeLock($type) { |
|
389 | + $this->view->changeLock($this->path, $type); |
|
390 | + } |
|
391 | + |
|
392 | + /** |
|
393 | + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE |
|
394 | + * @throws LockedException |
|
395 | + */ |
|
396 | + public function unlock($type) { |
|
397 | + $this->view->unlockFile($this->path, $type); |
|
398 | + } |
|
399 | + |
|
400 | + /** |
|
401 | + * @param string $targetPath |
|
402 | + * @return \OC\Files\Node\Node |
|
403 | + * @throws InvalidPathException |
|
404 | + * @throws NotFoundException |
|
405 | + * @throws NotPermittedException if copy not allowed or failed |
|
406 | + */ |
|
407 | + public function copy($targetPath) { |
|
408 | + $targetPath = $this->normalizePath($targetPath); |
|
409 | + $parent = $this->root->get(dirname($targetPath)); |
|
410 | + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { |
|
411 | + $nonExisting = $this->createNonExistingNode($targetPath); |
|
412 | + $this->sendHooks(['preCopy'], [$this, $nonExisting]); |
|
413 | + $this->sendHooks(['preWrite'], [$nonExisting]); |
|
414 | + if (!$this->view->copy($this->path, $targetPath)) { |
|
415 | + throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath); |
|
416 | + } |
|
417 | + $targetNode = $this->root->get($targetPath); |
|
418 | + $this->sendHooks(['postCopy'], [$this, $targetNode]); |
|
419 | + $this->sendHooks(['postWrite'], [$targetNode]); |
|
420 | + return $targetNode; |
|
421 | + } else { |
|
422 | + throw new NotPermittedException('No permission to copy to path ' . $targetPath); |
|
423 | + } |
|
424 | + } |
|
425 | + |
|
426 | + /** |
|
427 | + * @param string $targetPath |
|
428 | + * @return \OC\Files\Node\Node |
|
429 | + * @throws InvalidPathException |
|
430 | + * @throws NotFoundException |
|
431 | + * @throws NotPermittedException if move not allowed or failed |
|
432 | + * @throws LockedException |
|
433 | + */ |
|
434 | + public function move($targetPath) { |
|
435 | + $targetPath = $this->normalizePath($targetPath); |
|
436 | + $parent = $this->root->get(dirname($targetPath)); |
|
437 | + if ( |
|
438 | + $parent instanceof Folder and |
|
439 | + $this->isValidPath($targetPath) and |
|
440 | + ( |
|
441 | + $parent->isCreatable() || |
|
442 | + ($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount) |
|
443 | + ) |
|
444 | + ) { |
|
445 | + $nonExisting = $this->createNonExistingNode($targetPath); |
|
446 | + $this->sendHooks(['preRename'], [$this, $nonExisting]); |
|
447 | + $this->sendHooks(['preWrite'], [$nonExisting]); |
|
448 | + if (!$this->view->rename($this->path, $targetPath)) { |
|
449 | + throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); |
|
450 | + } |
|
451 | + $targetNode = $this->root->get($targetPath); |
|
452 | + $this->sendHooks(['postRename'], [$this, $targetNode]); |
|
453 | + $this->sendHooks(['postWrite'], [$targetNode]); |
|
454 | + $this->path = $targetPath; |
|
455 | + return $targetNode; |
|
456 | + } else { |
|
457 | + throw new NotPermittedException('No permission to move to path ' . $targetPath); |
|
458 | + } |
|
459 | + } |
|
460 | + |
|
461 | + public function getCreationTime(): int { |
|
462 | + return $this->getFileInfo()->getCreationTime(); |
|
463 | + } |
|
464 | + |
|
465 | + public function getUploadTime(): int { |
|
466 | + return $this->getFileInfo()->getUploadTime(); |
|
467 | + } |
|
468 | 468 | } |
@@ -115,7 +115,7 @@ discard block |
||
115 | 115 | $dispatcher = \OC::$server->getEventDispatcher(); |
116 | 116 | foreach ($hooks as $hook) { |
117 | 117 | $this->root->emit('\OC\Files', $hook, $args); |
118 | - $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); |
|
118 | + $dispatcher->dispatch('\OCP\Files::'.$hook, new GenericEvent($args)); |
|
119 | 119 | } |
120 | 120 | } |
121 | 121 | |
@@ -159,7 +159,7 @@ discard block |
||
159 | 159 | * @throws \OCP\Files\NotFoundException |
160 | 160 | */ |
161 | 161 | public function getStorage() { |
162 | - [$storage,] = $this->view->resolvePath($this->path); |
|
162 | + [$storage, ] = $this->view->resolvePath($this->path); |
|
163 | 163 | return $storage; |
164 | 164 | } |
165 | 165 | |
@@ -306,7 +306,7 @@ discard block |
||
306 | 306 | $path = str_replace('\\', '/', $path); |
307 | 307 | //add leading slash |
308 | 308 | if ($path[0] !== '/') { |
309 | - $path = '/' . $path; |
|
309 | + $path = '/'.$path; |
|
310 | 310 | } |
311 | 311 | //remove duplicate slashes |
312 | 312 | while (strpos($path, '//') !== false) { |
@@ -326,7 +326,7 @@ discard block |
||
326 | 326 | */ |
327 | 327 | public function isValidPath($path) { |
328 | 328 | if (!$path || $path[0] !== '/') { |
329 | - $path = '/' . $path; |
|
329 | + $path = '/'.$path; |
|
330 | 330 | } |
331 | 331 | if (strstr($path, '/../') || strrchr($path, '/') === '/..') { |
332 | 332 | return false; |
@@ -412,14 +412,14 @@ discard block |
||
412 | 412 | $this->sendHooks(['preCopy'], [$this, $nonExisting]); |
413 | 413 | $this->sendHooks(['preWrite'], [$nonExisting]); |
414 | 414 | if (!$this->view->copy($this->path, $targetPath)) { |
415 | - throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath); |
|
415 | + throw new NotPermittedException('Could not copy '.$this->path.' to '.$targetPath); |
|
416 | 416 | } |
417 | 417 | $targetNode = $this->root->get($targetPath); |
418 | 418 | $this->sendHooks(['postCopy'], [$this, $targetNode]); |
419 | 419 | $this->sendHooks(['postWrite'], [$targetNode]); |
420 | 420 | return $targetNode; |
421 | 421 | } else { |
422 | - throw new NotPermittedException('No permission to copy to path ' . $targetPath); |
|
422 | + throw new NotPermittedException('No permission to copy to path '.$targetPath); |
|
423 | 423 | } |
424 | 424 | } |
425 | 425 | |
@@ -446,7 +446,7 @@ discard block |
||
446 | 446 | $this->sendHooks(['preRename'], [$this, $nonExisting]); |
447 | 447 | $this->sendHooks(['preWrite'], [$nonExisting]); |
448 | 448 | if (!$this->view->rename($this->path, $targetPath)) { |
449 | - throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); |
|
449 | + throw new NotPermittedException('Could not move '.$this->path.' to '.$targetPath); |
|
450 | 450 | } |
451 | 451 | $targetNode = $this->root->get($targetPath); |
452 | 452 | $this->sendHooks(['postRename'], [$this, $targetNode]); |
@@ -454,7 +454,7 @@ discard block |
||
454 | 454 | $this->path = $targetPath; |
455 | 455 | return $targetNode; |
456 | 456 | } else { |
457 | - throw new NotPermittedException('No permission to move to path ' . $targetPath); |
|
457 | + throw new NotPermittedException('No permission to move to path '.$targetPath); |
|
458 | 458 | } |
459 | 459 | } |
460 | 460 |
@@ -39,118 +39,118 @@ |
||
39 | 39 | |
40 | 40 | abstract class AbstractDatabase { |
41 | 41 | |
42 | - /** @var IL10N */ |
|
43 | - protected $trans; |
|
44 | - /** @var string */ |
|
45 | - protected $dbUser; |
|
46 | - /** @var string */ |
|
47 | - protected $dbPassword; |
|
48 | - /** @var string */ |
|
49 | - protected $dbName; |
|
50 | - /** @var string */ |
|
51 | - protected $dbHost; |
|
52 | - /** @var string */ |
|
53 | - protected $dbPort; |
|
54 | - /** @var string */ |
|
55 | - protected $tablePrefix; |
|
56 | - /** @var SystemConfig */ |
|
57 | - protected $config; |
|
58 | - /** @var ILogger */ |
|
59 | - protected $logger; |
|
60 | - /** @var ISecureRandom */ |
|
61 | - protected $random; |
|
42 | + /** @var IL10N */ |
|
43 | + protected $trans; |
|
44 | + /** @var string */ |
|
45 | + protected $dbUser; |
|
46 | + /** @var string */ |
|
47 | + protected $dbPassword; |
|
48 | + /** @var string */ |
|
49 | + protected $dbName; |
|
50 | + /** @var string */ |
|
51 | + protected $dbHost; |
|
52 | + /** @var string */ |
|
53 | + protected $dbPort; |
|
54 | + /** @var string */ |
|
55 | + protected $tablePrefix; |
|
56 | + /** @var SystemConfig */ |
|
57 | + protected $config; |
|
58 | + /** @var ILogger */ |
|
59 | + protected $logger; |
|
60 | + /** @var ISecureRandom */ |
|
61 | + protected $random; |
|
62 | 62 | |
63 | - public function __construct(IL10N $trans, SystemConfig $config, ILogger $logger, ISecureRandom $random) { |
|
64 | - $this->trans = $trans; |
|
65 | - $this->config = $config; |
|
66 | - $this->logger = $logger; |
|
67 | - $this->random = $random; |
|
68 | - } |
|
63 | + public function __construct(IL10N $trans, SystemConfig $config, ILogger $logger, ISecureRandom $random) { |
|
64 | + $this->trans = $trans; |
|
65 | + $this->config = $config; |
|
66 | + $this->logger = $logger; |
|
67 | + $this->random = $random; |
|
68 | + } |
|
69 | 69 | |
70 | - public function validate($config) { |
|
71 | - $errors = []; |
|
72 | - if (empty($config['dbuser']) && empty($config['dbname'])) { |
|
73 | - $errors[] = $this->trans->t("%s enter the database username and name.", [$this->dbprettyname]); |
|
74 | - } elseif (empty($config['dbuser'])) { |
|
75 | - $errors[] = $this->trans->t("%s enter the database username.", [$this->dbprettyname]); |
|
76 | - } elseif (empty($config['dbname'])) { |
|
77 | - $errors[] = $this->trans->t("%s enter the database name.", [$this->dbprettyname]); |
|
78 | - } |
|
79 | - if (substr_count($config['dbname'], '.') >= 1) { |
|
80 | - $errors[] = $this->trans->t("%s you may not use dots in the database name", [$this->dbprettyname]); |
|
81 | - } |
|
82 | - return $errors; |
|
83 | - } |
|
70 | + public function validate($config) { |
|
71 | + $errors = []; |
|
72 | + if (empty($config['dbuser']) && empty($config['dbname'])) { |
|
73 | + $errors[] = $this->trans->t("%s enter the database username and name.", [$this->dbprettyname]); |
|
74 | + } elseif (empty($config['dbuser'])) { |
|
75 | + $errors[] = $this->trans->t("%s enter the database username.", [$this->dbprettyname]); |
|
76 | + } elseif (empty($config['dbname'])) { |
|
77 | + $errors[] = $this->trans->t("%s enter the database name.", [$this->dbprettyname]); |
|
78 | + } |
|
79 | + if (substr_count($config['dbname'], '.') >= 1) { |
|
80 | + $errors[] = $this->trans->t("%s you may not use dots in the database name", [$this->dbprettyname]); |
|
81 | + } |
|
82 | + return $errors; |
|
83 | + } |
|
84 | 84 | |
85 | - public function initialize($config) { |
|
86 | - $dbUser = $config['dbuser']; |
|
87 | - $dbPass = $config['dbpass']; |
|
88 | - $dbName = $config['dbname']; |
|
89 | - $dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost'; |
|
90 | - $dbPort = !empty($config['dbport']) ? $config['dbport'] : ''; |
|
91 | - $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_'; |
|
85 | + public function initialize($config) { |
|
86 | + $dbUser = $config['dbuser']; |
|
87 | + $dbPass = $config['dbpass']; |
|
88 | + $dbName = $config['dbname']; |
|
89 | + $dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost'; |
|
90 | + $dbPort = !empty($config['dbport']) ? $config['dbport'] : ''; |
|
91 | + $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_'; |
|
92 | 92 | |
93 | - $this->config->setValues([ |
|
94 | - 'dbname' => $dbName, |
|
95 | - 'dbhost' => $dbHost, |
|
96 | - 'dbport' => $dbPort, |
|
97 | - 'dbtableprefix' => $dbTablePrefix, |
|
98 | - ]); |
|
93 | + $this->config->setValues([ |
|
94 | + 'dbname' => $dbName, |
|
95 | + 'dbhost' => $dbHost, |
|
96 | + 'dbport' => $dbPort, |
|
97 | + 'dbtableprefix' => $dbTablePrefix, |
|
98 | + ]); |
|
99 | 99 | |
100 | - $this->dbUser = $dbUser; |
|
101 | - $this->dbPassword = $dbPass; |
|
102 | - $this->dbName = $dbName; |
|
103 | - $this->dbHost = $dbHost; |
|
104 | - $this->dbPort = $dbPort; |
|
105 | - $this->tablePrefix = $dbTablePrefix; |
|
106 | - } |
|
100 | + $this->dbUser = $dbUser; |
|
101 | + $this->dbPassword = $dbPass; |
|
102 | + $this->dbName = $dbName; |
|
103 | + $this->dbHost = $dbHost; |
|
104 | + $this->dbPort = $dbPort; |
|
105 | + $this->tablePrefix = $dbTablePrefix; |
|
106 | + } |
|
107 | 107 | |
108 | - /** |
|
109 | - * @param array $configOverwrite |
|
110 | - * @return \OC\DB\Connection |
|
111 | - */ |
|
112 | - protected function connect(array $configOverwrite = []): Connection { |
|
113 | - $connectionParams = [ |
|
114 | - 'host' => $this->dbHost, |
|
115 | - 'user' => $this->dbUser, |
|
116 | - 'password' => $this->dbPassword, |
|
117 | - 'tablePrefix' => $this->tablePrefix, |
|
118 | - 'dbname' => $this->dbName |
|
119 | - ]; |
|
108 | + /** |
|
109 | + * @param array $configOverwrite |
|
110 | + * @return \OC\DB\Connection |
|
111 | + */ |
|
112 | + protected function connect(array $configOverwrite = []): Connection { |
|
113 | + $connectionParams = [ |
|
114 | + 'host' => $this->dbHost, |
|
115 | + 'user' => $this->dbUser, |
|
116 | + 'password' => $this->dbPassword, |
|
117 | + 'tablePrefix' => $this->tablePrefix, |
|
118 | + 'dbname' => $this->dbName |
|
119 | + ]; |
|
120 | 120 | |
121 | - // adding port support through installer |
|
122 | - if (!empty($this->dbPort)) { |
|
123 | - if (ctype_digit($this->dbPort)) { |
|
124 | - $connectionParams['port'] = $this->dbPort; |
|
125 | - } else { |
|
126 | - $connectionParams['unix_socket'] = $this->dbPort; |
|
127 | - } |
|
128 | - } elseif (strpos($this->dbHost, ':')) { |
|
129 | - // Host variable may carry a port or socket. |
|
130 | - [$host, $portOrSocket] = explode(':', $this->dbHost, 2); |
|
131 | - if (ctype_digit($portOrSocket)) { |
|
132 | - $connectionParams['port'] = $portOrSocket; |
|
133 | - } else { |
|
134 | - $connectionParams['unix_socket'] = $portOrSocket; |
|
135 | - } |
|
136 | - $connectionParams['host'] = $host; |
|
137 | - } |
|
121 | + // adding port support through installer |
|
122 | + if (!empty($this->dbPort)) { |
|
123 | + if (ctype_digit($this->dbPort)) { |
|
124 | + $connectionParams['port'] = $this->dbPort; |
|
125 | + } else { |
|
126 | + $connectionParams['unix_socket'] = $this->dbPort; |
|
127 | + } |
|
128 | + } elseif (strpos($this->dbHost, ':')) { |
|
129 | + // Host variable may carry a port or socket. |
|
130 | + [$host, $portOrSocket] = explode(':', $this->dbHost, 2); |
|
131 | + if (ctype_digit($portOrSocket)) { |
|
132 | + $connectionParams['port'] = $portOrSocket; |
|
133 | + } else { |
|
134 | + $connectionParams['unix_socket'] = $portOrSocket; |
|
135 | + } |
|
136 | + $connectionParams['host'] = $host; |
|
137 | + } |
|
138 | 138 | |
139 | - $connectionParams = array_merge($connectionParams, $configOverwrite); |
|
140 | - $cf = new ConnectionFactory($this->config); |
|
141 | - return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams); |
|
142 | - } |
|
139 | + $connectionParams = array_merge($connectionParams, $configOverwrite); |
|
140 | + $cf = new ConnectionFactory($this->config); |
|
141 | + return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams); |
|
142 | + } |
|
143 | 143 | |
144 | - /** |
|
145 | - * @param string $userName |
|
146 | - */ |
|
147 | - abstract public function setupDatabase($userName); |
|
144 | + /** |
|
145 | + * @param string $userName |
|
146 | + */ |
|
147 | + abstract public function setupDatabase($userName); |
|
148 | 148 | |
149 | - public function runMigrations() { |
|
150 | - if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) { |
|
151 | - return; |
|
152 | - } |
|
153 | - $ms = new MigrationService('core', \OC::$server->get(Connection::class)); |
|
154 | - $ms->migrate('latest', true); |
|
155 | - } |
|
149 | + public function runMigrations() { |
|
150 | + if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) { |
|
151 | + return; |
|
152 | + } |
|
153 | + $ms = new MigrationService('core', \OC::$server->get(Connection::class)); |
|
154 | + $ms->migrate('latest', true); |
|
155 | + } |
|
156 | 156 | } |
@@ -46,403 +46,403 @@ |
||
46 | 46 | use Symfony\Component\Routing\RouteCollection; |
47 | 47 | |
48 | 48 | class Router implements IRouter { |
49 | - /** @var RouteCollection[] */ |
|
50 | - protected $collections = []; |
|
51 | - /** @var null|RouteCollection */ |
|
52 | - protected $collection = null; |
|
53 | - /** @var null|string */ |
|
54 | - protected $collectionName = null; |
|
55 | - /** @var null|RouteCollection */ |
|
56 | - protected $root = null; |
|
57 | - /** @var null|UrlGenerator */ |
|
58 | - protected $generator = null; |
|
59 | - /** @var string[] */ |
|
60 | - protected $routingFiles; |
|
61 | - /** @var bool */ |
|
62 | - protected $loaded = false; |
|
63 | - /** @var array */ |
|
64 | - protected $loadedApps = []; |
|
65 | - /** @var ILogger */ |
|
66 | - protected $logger; |
|
67 | - /** @var RequestContext */ |
|
68 | - protected $context; |
|
69 | - |
|
70 | - /** |
|
71 | - * @param ILogger $logger |
|
72 | - */ |
|
73 | - public function __construct(ILogger $logger) { |
|
74 | - $this->logger = $logger; |
|
75 | - $baseUrl = \OC::$WEBROOT; |
|
76 | - if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { |
|
77 | - $baseUrl .= '/index.php'; |
|
78 | - } |
|
79 | - if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) { |
|
80 | - $method = $_SERVER['REQUEST_METHOD']; |
|
81 | - } else { |
|
82 | - $method = 'GET'; |
|
83 | - } |
|
84 | - $request = \OC::$server->getRequest(); |
|
85 | - $host = $request->getServerHost(); |
|
86 | - $schema = $request->getServerProtocol(); |
|
87 | - $this->context = new RequestContext($baseUrl, $method, $host, $schema); |
|
88 | - // TODO cache |
|
89 | - $this->root = $this->getCollection('root'); |
|
90 | - } |
|
91 | - |
|
92 | - /** |
|
93 | - * Get the files to load the routes from |
|
94 | - * |
|
95 | - * @return string[] |
|
96 | - */ |
|
97 | - public function getRoutingFiles() { |
|
98 | - if (!isset($this->routingFiles)) { |
|
99 | - $this->routingFiles = []; |
|
100 | - foreach (\OC_APP::getEnabledApps() as $app) { |
|
101 | - $appPath = \OC_App::getAppPath($app); |
|
102 | - if ($appPath !== false) { |
|
103 | - $file = $appPath . '/appinfo/routes.php'; |
|
104 | - if (file_exists($file)) { |
|
105 | - $this->routingFiles[$app] = $file; |
|
106 | - } |
|
107 | - } |
|
108 | - } |
|
109 | - } |
|
110 | - return $this->routingFiles; |
|
111 | - } |
|
112 | - |
|
113 | - /** |
|
114 | - * Loads the routes |
|
115 | - * |
|
116 | - * @param null|string $app |
|
117 | - */ |
|
118 | - public function loadRoutes($app = null) { |
|
119 | - if (is_string($app)) { |
|
120 | - $app = \OC_App::cleanAppId($app); |
|
121 | - } |
|
122 | - |
|
123 | - $requestedApp = $app; |
|
124 | - if ($this->loaded) { |
|
125 | - return; |
|
126 | - } |
|
127 | - if (is_null($app)) { |
|
128 | - $this->loaded = true; |
|
129 | - $routingFiles = $this->getRoutingFiles(); |
|
130 | - } else { |
|
131 | - if (isset($this->loadedApps[$app])) { |
|
132 | - return; |
|
133 | - } |
|
134 | - $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; |
|
135 | - if ($file !== false && file_exists($file)) { |
|
136 | - $routingFiles = [$app => $file]; |
|
137 | - } else { |
|
138 | - $routingFiles = []; |
|
139 | - } |
|
140 | - } |
|
141 | - \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); |
|
142 | - foreach ($routingFiles as $app => $file) { |
|
143 | - if (!isset($this->loadedApps[$app])) { |
|
144 | - if (!\OC_App::isAppLoaded($app)) { |
|
145 | - // app MUST be loaded before app routes |
|
146 | - // try again next time loadRoutes() is called |
|
147 | - $this->loaded = false; |
|
148 | - continue; |
|
149 | - } |
|
150 | - $this->loadedApps[$app] = true; |
|
151 | - $this->useCollection($app); |
|
152 | - $this->requireRouteFile($file, $app); |
|
153 | - $collection = $this->getCollection($app); |
|
154 | - $this->root->addCollection($collection); |
|
155 | - |
|
156 | - // Also add the OCS collection |
|
157 | - $collection = $this->getCollection($app.'.ocs'); |
|
158 | - $collection->addPrefix('/ocsapp'); |
|
159 | - $this->root->addCollection($collection); |
|
160 | - } |
|
161 | - } |
|
162 | - if (!isset($this->loadedApps['core'])) { |
|
163 | - $this->loadedApps['core'] = true; |
|
164 | - $this->useCollection('root'); |
|
165 | - require_once __DIR__ . '/../../../core/routes.php'; |
|
166 | - |
|
167 | - // Also add the OCS collection |
|
168 | - $collection = $this->getCollection('root.ocs'); |
|
169 | - $collection->addPrefix('/ocsapp'); |
|
170 | - $this->root->addCollection($collection); |
|
171 | - } |
|
172 | - if ($this->loaded) { |
|
173 | - $collection = $this->getCollection('ocs'); |
|
174 | - $collection->addPrefix('/ocs'); |
|
175 | - $this->root->addCollection($collection); |
|
176 | - } |
|
177 | - \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp); |
|
178 | - } |
|
179 | - |
|
180 | - /** |
|
181 | - * @param string $name |
|
182 | - * @return \Symfony\Component\Routing\RouteCollection |
|
183 | - */ |
|
184 | - protected function getCollection($name) { |
|
185 | - if (!isset($this->collections[$name])) { |
|
186 | - $this->collections[$name] = new RouteCollection(); |
|
187 | - } |
|
188 | - return $this->collections[$name]; |
|
189 | - } |
|
190 | - |
|
191 | - /** |
|
192 | - * Sets the collection to use for adding routes |
|
193 | - * |
|
194 | - * @param string $name Name of the collection to use. |
|
195 | - * @return void |
|
196 | - */ |
|
197 | - public function useCollection($name) { |
|
198 | - $this->collection = $this->getCollection($name); |
|
199 | - $this->collectionName = $name; |
|
200 | - } |
|
201 | - |
|
202 | - /** |
|
203 | - * returns the current collection name in use for adding routes |
|
204 | - * |
|
205 | - * @return string the collection name |
|
206 | - */ |
|
207 | - public function getCurrentCollection() { |
|
208 | - return $this->collectionName; |
|
209 | - } |
|
210 | - |
|
211 | - |
|
212 | - /** |
|
213 | - * Create a \OC\Route\Route. |
|
214 | - * |
|
215 | - * @param string $name Name of the route to create. |
|
216 | - * @param string $pattern The pattern to match |
|
217 | - * @param array $defaults An array of default parameter values |
|
218 | - * @param array $requirements An array of requirements for parameters (regexes) |
|
219 | - * @return \OC\Route\Route |
|
220 | - */ |
|
221 | - public function create($name, |
|
222 | - $pattern, |
|
223 | - array $defaults = [], |
|
224 | - array $requirements = []) { |
|
225 | - $route = new Route($pattern, $defaults, $requirements); |
|
226 | - $this->collection->add($name, $route); |
|
227 | - return $route; |
|
228 | - } |
|
229 | - |
|
230 | - /** |
|
231 | - * Find the route matching $url |
|
232 | - * |
|
233 | - * @param string $url The url to find |
|
234 | - * @throws \Exception |
|
235 | - * @return array |
|
236 | - */ |
|
237 | - public function findMatchingRoute(string $url): array { |
|
238 | - if (substr($url, 0, 6) === '/apps/') { |
|
239 | - // empty string / 'apps' / $app / rest of the route |
|
240 | - [, , $app,] = explode('/', $url, 4); |
|
241 | - |
|
242 | - $app = \OC_App::cleanAppId($app); |
|
243 | - \OC::$REQUESTEDAPP = $app; |
|
244 | - $this->loadRoutes($app); |
|
245 | - } elseif (substr($url, 0, 13) === '/ocsapp/apps/') { |
|
246 | - // empty string / 'ocsapp' / 'apps' / $app / rest of the route |
|
247 | - [, , , $app,] = explode('/', $url, 5); |
|
248 | - |
|
249 | - $app = \OC_App::cleanAppId($app); |
|
250 | - \OC::$REQUESTEDAPP = $app; |
|
251 | - $this->loadRoutes($app); |
|
252 | - } elseif (substr($url, 0, 10) === '/settings/') { |
|
253 | - $this->loadRoutes('settings'); |
|
254 | - } elseif (substr($url, 0, 6) === '/core/') { |
|
255 | - \OC::$REQUESTEDAPP = $url; |
|
256 | - if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) { |
|
257 | - \OC_App::loadApps(); |
|
258 | - } |
|
259 | - $this->loadRoutes('core'); |
|
260 | - } else { |
|
261 | - $this->loadRoutes(); |
|
262 | - } |
|
263 | - |
|
264 | - $matcher = new UrlMatcher($this->root, $this->context); |
|
265 | - try { |
|
266 | - $parameters = $matcher->match($url); |
|
267 | - } catch (ResourceNotFoundException $e) { |
|
268 | - if (substr($url, -1) !== '/') { |
|
269 | - // We allow links to apps/files? for backwards compatibility reasons |
|
270 | - // However, since Symfony does not allow empty route names, the route |
|
271 | - // we need to match is '/', so we need to append the '/' here. |
|
272 | - try { |
|
273 | - $parameters = $matcher->match($url . '/'); |
|
274 | - } catch (ResourceNotFoundException $newException) { |
|
275 | - // If we still didn't match a route, we throw the original exception |
|
276 | - throw $e; |
|
277 | - } |
|
278 | - } else { |
|
279 | - throw $e; |
|
280 | - } |
|
281 | - } |
|
282 | - |
|
283 | - return $parameters; |
|
284 | - } |
|
285 | - |
|
286 | - /** |
|
287 | - * Find and execute the route matching $url |
|
288 | - * |
|
289 | - * @param string $url The url to find |
|
290 | - * @throws \Exception |
|
291 | - * @return void |
|
292 | - */ |
|
293 | - public function match($url) { |
|
294 | - $parameters = $this->findMatchingRoute($url); |
|
295 | - |
|
296 | - \OC::$server->getEventLogger()->start('run_route', 'Run route'); |
|
297 | - if (isset($parameters['caller'])) { |
|
298 | - $caller = $parameters['caller']; |
|
299 | - unset($parameters['caller']); |
|
300 | - unset($parameters['action']); |
|
301 | - $application = $this->getApplicationClass($caller[0]); |
|
302 | - \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters); |
|
303 | - } elseif (isset($parameters['action'])) { |
|
304 | - $action = $parameters['action']; |
|
305 | - if (!is_callable($action)) { |
|
306 | - throw new \Exception('not a callable action'); |
|
307 | - } |
|
308 | - unset($parameters['action']); |
|
309 | - unset($parameters['caller']); |
|
310 | - call_user_func($action, $parameters); |
|
311 | - } elseif (isset($parameters['file'])) { |
|
312 | - include $parameters['file']; |
|
313 | - } else { |
|
314 | - throw new \Exception('no action available'); |
|
315 | - } |
|
316 | - \OC::$server->getEventLogger()->end('run_route'); |
|
317 | - } |
|
318 | - |
|
319 | - /** |
|
320 | - * Get the url generator |
|
321 | - * |
|
322 | - * @return \Symfony\Component\Routing\Generator\UrlGenerator |
|
323 | - * |
|
324 | - */ |
|
325 | - public function getGenerator() { |
|
326 | - if (null !== $this->generator) { |
|
327 | - return $this->generator; |
|
328 | - } |
|
329 | - |
|
330 | - return $this->generator = new UrlGenerator($this->root, $this->context); |
|
331 | - } |
|
332 | - |
|
333 | - /** |
|
334 | - * Generate url based on $name and $parameters |
|
335 | - * |
|
336 | - * @param string $name Name of the route to use. |
|
337 | - * @param array $parameters Parameters for the route |
|
338 | - * @param bool $absolute |
|
339 | - * @return string |
|
340 | - */ |
|
341 | - public function generate($name, |
|
342 | - $parameters = [], |
|
343 | - $absolute = false) { |
|
344 | - $referenceType = UrlGenerator::ABSOLUTE_URL; |
|
345 | - if ($absolute === false) { |
|
346 | - $referenceType = UrlGenerator::ABSOLUTE_PATH; |
|
347 | - } |
|
348 | - $name = $this->fixLegacyRootName($name); |
|
349 | - if (strpos($name, '.') !== false) { |
|
350 | - [$appName, $other] = explode('.', $name, 3); |
|
351 | - // OCS routes are prefixed with "ocs." |
|
352 | - if ($appName === 'ocs') { |
|
353 | - $appName = $other; |
|
354 | - } |
|
355 | - $this->loadRoutes($appName); |
|
356 | - try { |
|
357 | - return $this->getGenerator()->generate($name, $parameters, $referenceType); |
|
358 | - } catch (RouteNotFoundException $e) { |
|
359 | - } |
|
360 | - } |
|
361 | - |
|
362 | - // Fallback load all routes |
|
363 | - $this->loadRoutes(); |
|
364 | - try { |
|
365 | - return $this->getGenerator()->generate($name, $parameters, $referenceType); |
|
366 | - } catch (RouteNotFoundException $e) { |
|
367 | - $this->logger->logException($e, ['level' => ILogger::INFO]); |
|
368 | - return ''; |
|
369 | - } |
|
370 | - } |
|
371 | - |
|
372 | - protected function fixLegacyRootName(string $routeName): string { |
|
373 | - if ($routeName === 'files.viewcontroller.showFile') { |
|
374 | - return 'files.View.showFile'; |
|
375 | - } |
|
376 | - if ($routeName === 'files_sharing.sharecontroller.showShare') { |
|
377 | - return 'files_sharing.Share.showShare'; |
|
378 | - } |
|
379 | - if ($routeName === 'files_sharing.sharecontroller.showAuthenticate') { |
|
380 | - return 'files_sharing.Share.showAuthenticate'; |
|
381 | - } |
|
382 | - if ($routeName === 'files_sharing.sharecontroller.authenticate') { |
|
383 | - return 'files_sharing.Share.authenticate'; |
|
384 | - } |
|
385 | - if ($routeName === 'files_sharing.sharecontroller.downloadShare') { |
|
386 | - return 'files_sharing.Share.downloadShare'; |
|
387 | - } |
|
388 | - if ($routeName === 'files_sharing.publicpreview.directLink') { |
|
389 | - return 'files_sharing.PublicPreview.directLink'; |
|
390 | - } |
|
391 | - if ($routeName === 'cloud_federation_api.requesthandlercontroller.addShare') { |
|
392 | - return 'cloud_federation_api.RequestHandler.addShare'; |
|
393 | - } |
|
394 | - if ($routeName === 'cloud_federation_api.requesthandlercontroller.receiveNotification') { |
|
395 | - return 'cloud_federation_api.RequestHandler.receiveNotification'; |
|
396 | - } |
|
397 | - return $routeName; |
|
398 | - } |
|
399 | - |
|
400 | - /** |
|
401 | - * To isolate the variable scope used inside the $file it is required in it's own method |
|
402 | - * |
|
403 | - * @param string $file the route file location to include |
|
404 | - * @param string $appName |
|
405 | - */ |
|
406 | - private function requireRouteFile($file, $appName) { |
|
407 | - $this->setupRoutes(include_once $file, $appName); |
|
408 | - } |
|
409 | - |
|
410 | - |
|
411 | - /** |
|
412 | - * If a routes.php file returns an array, try to set up the application and |
|
413 | - * register the routes for the app. The application class will be chosen by |
|
414 | - * camelcasing the appname, e.g.: my_app will be turned into |
|
415 | - * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default |
|
416 | - * App will be intialized. This makes it optional to ship an |
|
417 | - * appinfo/application.php by using the built in query resolver |
|
418 | - * |
|
419 | - * @param array $routes the application routes |
|
420 | - * @param string $appName the name of the app. |
|
421 | - */ |
|
422 | - private function setupRoutes($routes, $appName) { |
|
423 | - if (is_array($routes)) { |
|
424 | - $routeParser = new RouteParser(); |
|
425 | - |
|
426 | - $defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName); |
|
427 | - $ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName); |
|
428 | - |
|
429 | - $this->root->addCollection($defaultRoutes); |
|
430 | - $ocsRoutes->addPrefix('/ocsapp'); |
|
431 | - $this->root->addCollection($ocsRoutes); |
|
432 | - } |
|
433 | - } |
|
434 | - |
|
435 | - private function getApplicationClass(string $appName) { |
|
436 | - $appNameSpace = App::buildAppNamespace($appName); |
|
437 | - |
|
438 | - $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; |
|
439 | - |
|
440 | - if (class_exists($applicationClassName)) { |
|
441 | - $application = \OC::$server->query($applicationClassName); |
|
442 | - } else { |
|
443 | - $application = new App($appName); |
|
444 | - } |
|
445 | - |
|
446 | - return $application; |
|
447 | - } |
|
49 | + /** @var RouteCollection[] */ |
|
50 | + protected $collections = []; |
|
51 | + /** @var null|RouteCollection */ |
|
52 | + protected $collection = null; |
|
53 | + /** @var null|string */ |
|
54 | + protected $collectionName = null; |
|
55 | + /** @var null|RouteCollection */ |
|
56 | + protected $root = null; |
|
57 | + /** @var null|UrlGenerator */ |
|
58 | + protected $generator = null; |
|
59 | + /** @var string[] */ |
|
60 | + protected $routingFiles; |
|
61 | + /** @var bool */ |
|
62 | + protected $loaded = false; |
|
63 | + /** @var array */ |
|
64 | + protected $loadedApps = []; |
|
65 | + /** @var ILogger */ |
|
66 | + protected $logger; |
|
67 | + /** @var RequestContext */ |
|
68 | + protected $context; |
|
69 | + |
|
70 | + /** |
|
71 | + * @param ILogger $logger |
|
72 | + */ |
|
73 | + public function __construct(ILogger $logger) { |
|
74 | + $this->logger = $logger; |
|
75 | + $baseUrl = \OC::$WEBROOT; |
|
76 | + if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { |
|
77 | + $baseUrl .= '/index.php'; |
|
78 | + } |
|
79 | + if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) { |
|
80 | + $method = $_SERVER['REQUEST_METHOD']; |
|
81 | + } else { |
|
82 | + $method = 'GET'; |
|
83 | + } |
|
84 | + $request = \OC::$server->getRequest(); |
|
85 | + $host = $request->getServerHost(); |
|
86 | + $schema = $request->getServerProtocol(); |
|
87 | + $this->context = new RequestContext($baseUrl, $method, $host, $schema); |
|
88 | + // TODO cache |
|
89 | + $this->root = $this->getCollection('root'); |
|
90 | + } |
|
91 | + |
|
92 | + /** |
|
93 | + * Get the files to load the routes from |
|
94 | + * |
|
95 | + * @return string[] |
|
96 | + */ |
|
97 | + public function getRoutingFiles() { |
|
98 | + if (!isset($this->routingFiles)) { |
|
99 | + $this->routingFiles = []; |
|
100 | + foreach (\OC_APP::getEnabledApps() as $app) { |
|
101 | + $appPath = \OC_App::getAppPath($app); |
|
102 | + if ($appPath !== false) { |
|
103 | + $file = $appPath . '/appinfo/routes.php'; |
|
104 | + if (file_exists($file)) { |
|
105 | + $this->routingFiles[$app] = $file; |
|
106 | + } |
|
107 | + } |
|
108 | + } |
|
109 | + } |
|
110 | + return $this->routingFiles; |
|
111 | + } |
|
112 | + |
|
113 | + /** |
|
114 | + * Loads the routes |
|
115 | + * |
|
116 | + * @param null|string $app |
|
117 | + */ |
|
118 | + public function loadRoutes($app = null) { |
|
119 | + if (is_string($app)) { |
|
120 | + $app = \OC_App::cleanAppId($app); |
|
121 | + } |
|
122 | + |
|
123 | + $requestedApp = $app; |
|
124 | + if ($this->loaded) { |
|
125 | + return; |
|
126 | + } |
|
127 | + if (is_null($app)) { |
|
128 | + $this->loaded = true; |
|
129 | + $routingFiles = $this->getRoutingFiles(); |
|
130 | + } else { |
|
131 | + if (isset($this->loadedApps[$app])) { |
|
132 | + return; |
|
133 | + } |
|
134 | + $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; |
|
135 | + if ($file !== false && file_exists($file)) { |
|
136 | + $routingFiles = [$app => $file]; |
|
137 | + } else { |
|
138 | + $routingFiles = []; |
|
139 | + } |
|
140 | + } |
|
141 | + \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); |
|
142 | + foreach ($routingFiles as $app => $file) { |
|
143 | + if (!isset($this->loadedApps[$app])) { |
|
144 | + if (!\OC_App::isAppLoaded($app)) { |
|
145 | + // app MUST be loaded before app routes |
|
146 | + // try again next time loadRoutes() is called |
|
147 | + $this->loaded = false; |
|
148 | + continue; |
|
149 | + } |
|
150 | + $this->loadedApps[$app] = true; |
|
151 | + $this->useCollection($app); |
|
152 | + $this->requireRouteFile($file, $app); |
|
153 | + $collection = $this->getCollection($app); |
|
154 | + $this->root->addCollection($collection); |
|
155 | + |
|
156 | + // Also add the OCS collection |
|
157 | + $collection = $this->getCollection($app.'.ocs'); |
|
158 | + $collection->addPrefix('/ocsapp'); |
|
159 | + $this->root->addCollection($collection); |
|
160 | + } |
|
161 | + } |
|
162 | + if (!isset($this->loadedApps['core'])) { |
|
163 | + $this->loadedApps['core'] = true; |
|
164 | + $this->useCollection('root'); |
|
165 | + require_once __DIR__ . '/../../../core/routes.php'; |
|
166 | + |
|
167 | + // Also add the OCS collection |
|
168 | + $collection = $this->getCollection('root.ocs'); |
|
169 | + $collection->addPrefix('/ocsapp'); |
|
170 | + $this->root->addCollection($collection); |
|
171 | + } |
|
172 | + if ($this->loaded) { |
|
173 | + $collection = $this->getCollection('ocs'); |
|
174 | + $collection->addPrefix('/ocs'); |
|
175 | + $this->root->addCollection($collection); |
|
176 | + } |
|
177 | + \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp); |
|
178 | + } |
|
179 | + |
|
180 | + /** |
|
181 | + * @param string $name |
|
182 | + * @return \Symfony\Component\Routing\RouteCollection |
|
183 | + */ |
|
184 | + protected function getCollection($name) { |
|
185 | + if (!isset($this->collections[$name])) { |
|
186 | + $this->collections[$name] = new RouteCollection(); |
|
187 | + } |
|
188 | + return $this->collections[$name]; |
|
189 | + } |
|
190 | + |
|
191 | + /** |
|
192 | + * Sets the collection to use for adding routes |
|
193 | + * |
|
194 | + * @param string $name Name of the collection to use. |
|
195 | + * @return void |
|
196 | + */ |
|
197 | + public function useCollection($name) { |
|
198 | + $this->collection = $this->getCollection($name); |
|
199 | + $this->collectionName = $name; |
|
200 | + } |
|
201 | + |
|
202 | + /** |
|
203 | + * returns the current collection name in use for adding routes |
|
204 | + * |
|
205 | + * @return string the collection name |
|
206 | + */ |
|
207 | + public function getCurrentCollection() { |
|
208 | + return $this->collectionName; |
|
209 | + } |
|
210 | + |
|
211 | + |
|
212 | + /** |
|
213 | + * Create a \OC\Route\Route. |
|
214 | + * |
|
215 | + * @param string $name Name of the route to create. |
|
216 | + * @param string $pattern The pattern to match |
|
217 | + * @param array $defaults An array of default parameter values |
|
218 | + * @param array $requirements An array of requirements for parameters (regexes) |
|
219 | + * @return \OC\Route\Route |
|
220 | + */ |
|
221 | + public function create($name, |
|
222 | + $pattern, |
|
223 | + array $defaults = [], |
|
224 | + array $requirements = []) { |
|
225 | + $route = new Route($pattern, $defaults, $requirements); |
|
226 | + $this->collection->add($name, $route); |
|
227 | + return $route; |
|
228 | + } |
|
229 | + |
|
230 | + /** |
|
231 | + * Find the route matching $url |
|
232 | + * |
|
233 | + * @param string $url The url to find |
|
234 | + * @throws \Exception |
|
235 | + * @return array |
|
236 | + */ |
|
237 | + public function findMatchingRoute(string $url): array { |
|
238 | + if (substr($url, 0, 6) === '/apps/') { |
|
239 | + // empty string / 'apps' / $app / rest of the route |
|
240 | + [, , $app,] = explode('/', $url, 4); |
|
241 | + |
|
242 | + $app = \OC_App::cleanAppId($app); |
|
243 | + \OC::$REQUESTEDAPP = $app; |
|
244 | + $this->loadRoutes($app); |
|
245 | + } elseif (substr($url, 0, 13) === '/ocsapp/apps/') { |
|
246 | + // empty string / 'ocsapp' / 'apps' / $app / rest of the route |
|
247 | + [, , , $app,] = explode('/', $url, 5); |
|
248 | + |
|
249 | + $app = \OC_App::cleanAppId($app); |
|
250 | + \OC::$REQUESTEDAPP = $app; |
|
251 | + $this->loadRoutes($app); |
|
252 | + } elseif (substr($url, 0, 10) === '/settings/') { |
|
253 | + $this->loadRoutes('settings'); |
|
254 | + } elseif (substr($url, 0, 6) === '/core/') { |
|
255 | + \OC::$REQUESTEDAPP = $url; |
|
256 | + if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) { |
|
257 | + \OC_App::loadApps(); |
|
258 | + } |
|
259 | + $this->loadRoutes('core'); |
|
260 | + } else { |
|
261 | + $this->loadRoutes(); |
|
262 | + } |
|
263 | + |
|
264 | + $matcher = new UrlMatcher($this->root, $this->context); |
|
265 | + try { |
|
266 | + $parameters = $matcher->match($url); |
|
267 | + } catch (ResourceNotFoundException $e) { |
|
268 | + if (substr($url, -1) !== '/') { |
|
269 | + // We allow links to apps/files? for backwards compatibility reasons |
|
270 | + // However, since Symfony does not allow empty route names, the route |
|
271 | + // we need to match is '/', so we need to append the '/' here. |
|
272 | + try { |
|
273 | + $parameters = $matcher->match($url . '/'); |
|
274 | + } catch (ResourceNotFoundException $newException) { |
|
275 | + // If we still didn't match a route, we throw the original exception |
|
276 | + throw $e; |
|
277 | + } |
|
278 | + } else { |
|
279 | + throw $e; |
|
280 | + } |
|
281 | + } |
|
282 | + |
|
283 | + return $parameters; |
|
284 | + } |
|
285 | + |
|
286 | + /** |
|
287 | + * Find and execute the route matching $url |
|
288 | + * |
|
289 | + * @param string $url The url to find |
|
290 | + * @throws \Exception |
|
291 | + * @return void |
|
292 | + */ |
|
293 | + public function match($url) { |
|
294 | + $parameters = $this->findMatchingRoute($url); |
|
295 | + |
|
296 | + \OC::$server->getEventLogger()->start('run_route', 'Run route'); |
|
297 | + if (isset($parameters['caller'])) { |
|
298 | + $caller = $parameters['caller']; |
|
299 | + unset($parameters['caller']); |
|
300 | + unset($parameters['action']); |
|
301 | + $application = $this->getApplicationClass($caller[0]); |
|
302 | + \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters); |
|
303 | + } elseif (isset($parameters['action'])) { |
|
304 | + $action = $parameters['action']; |
|
305 | + if (!is_callable($action)) { |
|
306 | + throw new \Exception('not a callable action'); |
|
307 | + } |
|
308 | + unset($parameters['action']); |
|
309 | + unset($parameters['caller']); |
|
310 | + call_user_func($action, $parameters); |
|
311 | + } elseif (isset($parameters['file'])) { |
|
312 | + include $parameters['file']; |
|
313 | + } else { |
|
314 | + throw new \Exception('no action available'); |
|
315 | + } |
|
316 | + \OC::$server->getEventLogger()->end('run_route'); |
|
317 | + } |
|
318 | + |
|
319 | + /** |
|
320 | + * Get the url generator |
|
321 | + * |
|
322 | + * @return \Symfony\Component\Routing\Generator\UrlGenerator |
|
323 | + * |
|
324 | + */ |
|
325 | + public function getGenerator() { |
|
326 | + if (null !== $this->generator) { |
|
327 | + return $this->generator; |
|
328 | + } |
|
329 | + |
|
330 | + return $this->generator = new UrlGenerator($this->root, $this->context); |
|
331 | + } |
|
332 | + |
|
333 | + /** |
|
334 | + * Generate url based on $name and $parameters |
|
335 | + * |
|
336 | + * @param string $name Name of the route to use. |
|
337 | + * @param array $parameters Parameters for the route |
|
338 | + * @param bool $absolute |
|
339 | + * @return string |
|
340 | + */ |
|
341 | + public function generate($name, |
|
342 | + $parameters = [], |
|
343 | + $absolute = false) { |
|
344 | + $referenceType = UrlGenerator::ABSOLUTE_URL; |
|
345 | + if ($absolute === false) { |
|
346 | + $referenceType = UrlGenerator::ABSOLUTE_PATH; |
|
347 | + } |
|
348 | + $name = $this->fixLegacyRootName($name); |
|
349 | + if (strpos($name, '.') !== false) { |
|
350 | + [$appName, $other] = explode('.', $name, 3); |
|
351 | + // OCS routes are prefixed with "ocs." |
|
352 | + if ($appName === 'ocs') { |
|
353 | + $appName = $other; |
|
354 | + } |
|
355 | + $this->loadRoutes($appName); |
|
356 | + try { |
|
357 | + return $this->getGenerator()->generate($name, $parameters, $referenceType); |
|
358 | + } catch (RouteNotFoundException $e) { |
|
359 | + } |
|
360 | + } |
|
361 | + |
|
362 | + // Fallback load all routes |
|
363 | + $this->loadRoutes(); |
|
364 | + try { |
|
365 | + return $this->getGenerator()->generate($name, $parameters, $referenceType); |
|
366 | + } catch (RouteNotFoundException $e) { |
|
367 | + $this->logger->logException($e, ['level' => ILogger::INFO]); |
|
368 | + return ''; |
|
369 | + } |
|
370 | + } |
|
371 | + |
|
372 | + protected function fixLegacyRootName(string $routeName): string { |
|
373 | + if ($routeName === 'files.viewcontroller.showFile') { |
|
374 | + return 'files.View.showFile'; |
|
375 | + } |
|
376 | + if ($routeName === 'files_sharing.sharecontroller.showShare') { |
|
377 | + return 'files_sharing.Share.showShare'; |
|
378 | + } |
|
379 | + if ($routeName === 'files_sharing.sharecontroller.showAuthenticate') { |
|
380 | + return 'files_sharing.Share.showAuthenticate'; |
|
381 | + } |
|
382 | + if ($routeName === 'files_sharing.sharecontroller.authenticate') { |
|
383 | + return 'files_sharing.Share.authenticate'; |
|
384 | + } |
|
385 | + if ($routeName === 'files_sharing.sharecontroller.downloadShare') { |
|
386 | + return 'files_sharing.Share.downloadShare'; |
|
387 | + } |
|
388 | + if ($routeName === 'files_sharing.publicpreview.directLink') { |
|
389 | + return 'files_sharing.PublicPreview.directLink'; |
|
390 | + } |
|
391 | + if ($routeName === 'cloud_federation_api.requesthandlercontroller.addShare') { |
|
392 | + return 'cloud_federation_api.RequestHandler.addShare'; |
|
393 | + } |
|
394 | + if ($routeName === 'cloud_federation_api.requesthandlercontroller.receiveNotification') { |
|
395 | + return 'cloud_federation_api.RequestHandler.receiveNotification'; |
|
396 | + } |
|
397 | + return $routeName; |
|
398 | + } |
|
399 | + |
|
400 | + /** |
|
401 | + * To isolate the variable scope used inside the $file it is required in it's own method |
|
402 | + * |
|
403 | + * @param string $file the route file location to include |
|
404 | + * @param string $appName |
|
405 | + */ |
|
406 | + private function requireRouteFile($file, $appName) { |
|
407 | + $this->setupRoutes(include_once $file, $appName); |
|
408 | + } |
|
409 | + |
|
410 | + |
|
411 | + /** |
|
412 | + * If a routes.php file returns an array, try to set up the application and |
|
413 | + * register the routes for the app. The application class will be chosen by |
|
414 | + * camelcasing the appname, e.g.: my_app will be turned into |
|
415 | + * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default |
|
416 | + * App will be intialized. This makes it optional to ship an |
|
417 | + * appinfo/application.php by using the built in query resolver |
|
418 | + * |
|
419 | + * @param array $routes the application routes |
|
420 | + * @param string $appName the name of the app. |
|
421 | + */ |
|
422 | + private function setupRoutes($routes, $appName) { |
|
423 | + if (is_array($routes)) { |
|
424 | + $routeParser = new RouteParser(); |
|
425 | + |
|
426 | + $defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName); |
|
427 | + $ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName); |
|
428 | + |
|
429 | + $this->root->addCollection($defaultRoutes); |
|
430 | + $ocsRoutes->addPrefix('/ocsapp'); |
|
431 | + $this->root->addCollection($ocsRoutes); |
|
432 | + } |
|
433 | + } |
|
434 | + |
|
435 | + private function getApplicationClass(string $appName) { |
|
436 | + $appNameSpace = App::buildAppNamespace($appName); |
|
437 | + |
|
438 | + $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; |
|
439 | + |
|
440 | + if (class_exists($applicationClassName)) { |
|
441 | + $application = \OC::$server->query($applicationClassName); |
|
442 | + } else { |
|
443 | + $application = new App($appName); |
|
444 | + } |
|
445 | + |
|
446 | + return $application; |
|
447 | + } |
|
448 | 448 | } |
@@ -100,7 +100,7 @@ discard block |
||
100 | 100 | foreach (\OC_APP::getEnabledApps() as $app) { |
101 | 101 | $appPath = \OC_App::getAppPath($app); |
102 | 102 | if ($appPath !== false) { |
103 | - $file = $appPath . '/appinfo/routes.php'; |
|
103 | + $file = $appPath.'/appinfo/routes.php'; |
|
104 | 104 | if (file_exists($file)) { |
105 | 105 | $this->routingFiles[$app] = $file; |
106 | 106 | } |
@@ -131,14 +131,14 @@ discard block |
||
131 | 131 | if (isset($this->loadedApps[$app])) { |
132 | 132 | return; |
133 | 133 | } |
134 | - $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; |
|
134 | + $file = \OC_App::getAppPath($app).'/appinfo/routes.php'; |
|
135 | 135 | if ($file !== false && file_exists($file)) { |
136 | 136 | $routingFiles = [$app => $file]; |
137 | 137 | } else { |
138 | 138 | $routingFiles = []; |
139 | 139 | } |
140 | 140 | } |
141 | - \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); |
|
141 | + \OC::$server->getEventLogger()->start('loadroutes'.$requestedApp, 'Loading Routes'); |
|
142 | 142 | foreach ($routingFiles as $app => $file) { |
143 | 143 | if (!isset($this->loadedApps[$app])) { |
144 | 144 | if (!\OC_App::isAppLoaded($app)) { |
@@ -162,7 +162,7 @@ discard block |
||
162 | 162 | if (!isset($this->loadedApps['core'])) { |
163 | 163 | $this->loadedApps['core'] = true; |
164 | 164 | $this->useCollection('root'); |
165 | - require_once __DIR__ . '/../../../core/routes.php'; |
|
165 | + require_once __DIR__.'/../../../core/routes.php'; |
|
166 | 166 | |
167 | 167 | // Also add the OCS collection |
168 | 168 | $collection = $this->getCollection('root.ocs'); |
@@ -174,7 +174,7 @@ discard block |
||
174 | 174 | $collection->addPrefix('/ocs'); |
175 | 175 | $this->root->addCollection($collection); |
176 | 176 | } |
177 | - \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp); |
|
177 | + \OC::$server->getEventLogger()->end('loadroutes'.$requestedApp); |
|
178 | 178 | } |
179 | 179 | |
180 | 180 | /** |
@@ -237,14 +237,14 @@ discard block |
||
237 | 237 | public function findMatchingRoute(string $url): array { |
238 | 238 | if (substr($url, 0, 6) === '/apps/') { |
239 | 239 | // empty string / 'apps' / $app / rest of the route |
240 | - [, , $app,] = explode('/', $url, 4); |
|
240 | + [,, $app, ] = explode('/', $url, 4); |
|
241 | 241 | |
242 | 242 | $app = \OC_App::cleanAppId($app); |
243 | 243 | \OC::$REQUESTEDAPP = $app; |
244 | 244 | $this->loadRoutes($app); |
245 | 245 | } elseif (substr($url, 0, 13) === '/ocsapp/apps/') { |
246 | 246 | // empty string / 'ocsapp' / 'apps' / $app / rest of the route |
247 | - [, , , $app,] = explode('/', $url, 5); |
|
247 | + [,,, $app, ] = explode('/', $url, 5); |
|
248 | 248 | |
249 | 249 | $app = \OC_App::cleanAppId($app); |
250 | 250 | \OC::$REQUESTEDAPP = $app; |
@@ -270,7 +270,7 @@ discard block |
||
270 | 270 | // However, since Symfony does not allow empty route names, the route |
271 | 271 | // we need to match is '/', so we need to append the '/' here. |
272 | 272 | try { |
273 | - $parameters = $matcher->match($url . '/'); |
|
273 | + $parameters = $matcher->match($url.'/'); |
|
274 | 274 | } catch (ResourceNotFoundException $newException) { |
275 | 275 | // If we still didn't match a route, we throw the original exception |
276 | 276 | throw $e; |
@@ -435,7 +435,7 @@ discard block |
||
435 | 435 | private function getApplicationClass(string $appName) { |
436 | 436 | $appNameSpace = App::buildAppNamespace($appName); |
437 | 437 | |
438 | - $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; |
|
438 | + $applicationClassName = $appNameSpace.'\\AppInfo\\Application'; |
|
439 | 439 | |
440 | 440 | if (class_exists($applicationClassName)) { |
441 | 441 | $application = \OC::$server->query($applicationClassName); |
@@ -39,258 +39,258 @@ |
||
39 | 39 | * @package OC\AppFramework\routing |
40 | 40 | */ |
41 | 41 | class RouteConfig { |
42 | - /** @var DIContainer */ |
|
43 | - private $container; |
|
44 | - |
|
45 | - /** @var Router */ |
|
46 | - private $router; |
|
47 | - |
|
48 | - /** @var array */ |
|
49 | - private $routes; |
|
50 | - |
|
51 | - /** @var string */ |
|
52 | - private $appName; |
|
53 | - |
|
54 | - /** @var string[] */ |
|
55 | - private $controllerNameCache = []; |
|
56 | - |
|
57 | - protected $rootUrlApps = [ |
|
58 | - 'cloud_federation_api', |
|
59 | - 'core', |
|
60 | - 'files_sharing', |
|
61 | - 'files', |
|
62 | - 'settings', |
|
63 | - 'spreed', |
|
64 | - ]; |
|
65 | - |
|
66 | - /** |
|
67 | - * @param \OC\AppFramework\DependencyInjection\DIContainer $container |
|
68 | - * @param \OC\Route\Router $router |
|
69 | - * @param array $routes |
|
70 | - * @internal param $appName |
|
71 | - */ |
|
72 | - public function __construct(DIContainer $container, Router $router, $routes) { |
|
73 | - $this->routes = $routes; |
|
74 | - $this->container = $container; |
|
75 | - $this->router = $router; |
|
76 | - $this->appName = $container['AppName']; |
|
77 | - } |
|
78 | - |
|
79 | - /** |
|
80 | - * The routes and resource will be registered to the \OCP\Route\IRouter |
|
81 | - */ |
|
82 | - public function register() { |
|
83 | - |
|
84 | - // parse simple |
|
85 | - $this->processIndexRoutes($this->routes); |
|
86 | - |
|
87 | - // parse resources |
|
88 | - $this->processIndexResources($this->routes); |
|
89 | - |
|
90 | - /* |
|
42 | + /** @var DIContainer */ |
|
43 | + private $container; |
|
44 | + |
|
45 | + /** @var Router */ |
|
46 | + private $router; |
|
47 | + |
|
48 | + /** @var array */ |
|
49 | + private $routes; |
|
50 | + |
|
51 | + /** @var string */ |
|
52 | + private $appName; |
|
53 | + |
|
54 | + /** @var string[] */ |
|
55 | + private $controllerNameCache = []; |
|
56 | + |
|
57 | + protected $rootUrlApps = [ |
|
58 | + 'cloud_federation_api', |
|
59 | + 'core', |
|
60 | + 'files_sharing', |
|
61 | + 'files', |
|
62 | + 'settings', |
|
63 | + 'spreed', |
|
64 | + ]; |
|
65 | + |
|
66 | + /** |
|
67 | + * @param \OC\AppFramework\DependencyInjection\DIContainer $container |
|
68 | + * @param \OC\Route\Router $router |
|
69 | + * @param array $routes |
|
70 | + * @internal param $appName |
|
71 | + */ |
|
72 | + public function __construct(DIContainer $container, Router $router, $routes) { |
|
73 | + $this->routes = $routes; |
|
74 | + $this->container = $container; |
|
75 | + $this->router = $router; |
|
76 | + $this->appName = $container['AppName']; |
|
77 | + } |
|
78 | + |
|
79 | + /** |
|
80 | + * The routes and resource will be registered to the \OCP\Route\IRouter |
|
81 | + */ |
|
82 | + public function register() { |
|
83 | + |
|
84 | + // parse simple |
|
85 | + $this->processIndexRoutes($this->routes); |
|
86 | + |
|
87 | + // parse resources |
|
88 | + $this->processIndexResources($this->routes); |
|
89 | + |
|
90 | + /* |
|
91 | 91 | * OCS routes go into a different collection |
92 | 92 | */ |
93 | - $oldCollection = $this->router->getCurrentCollection(); |
|
94 | - $this->router->useCollection($oldCollection . '.ocs'); |
|
95 | - |
|
96 | - // parse ocs simple routes |
|
97 | - $this->processOCS($this->routes); |
|
98 | - |
|
99 | - // parse ocs simple routes |
|
100 | - $this->processOCSResources($this->routes); |
|
101 | - |
|
102 | - $this->router->useCollection($oldCollection); |
|
103 | - } |
|
104 | - |
|
105 | - private function processOCS(array $routes): void { |
|
106 | - $ocsRoutes = $routes['ocs'] ?? []; |
|
107 | - foreach ($ocsRoutes as $ocsRoute) { |
|
108 | - $this->processRoute($ocsRoute, 'ocs.'); |
|
109 | - } |
|
110 | - } |
|
111 | - |
|
112 | - /** |
|
113 | - * Creates one route base on the give configuration |
|
114 | - * @param array $routes |
|
115 | - * @throws \UnexpectedValueException |
|
116 | - */ |
|
117 | - private function processIndexRoutes(array $routes): void { |
|
118 | - $simpleRoutes = $routes['routes'] ?? []; |
|
119 | - foreach ($simpleRoutes as $simpleRoute) { |
|
120 | - $this->processRoute($simpleRoute); |
|
121 | - } |
|
122 | - } |
|
123 | - |
|
124 | - protected function processRoute(array $route, string $routeNamePrefix = ''): void { |
|
125 | - $name = $route['name']; |
|
126 | - $postfix = $route['postfix'] ?? ''; |
|
127 | - $root = $this->buildRootPrefix($route, $routeNamePrefix); |
|
128 | - |
|
129 | - $url = $root . '/' . ltrim($route['url'], '/'); |
|
130 | - $verb = strtoupper($route['verb'] ?? 'GET'); |
|
131 | - |
|
132 | - $split = explode('#', $name, 2); |
|
133 | - if (count($split) !== 2) { |
|
134 | - throw new \UnexpectedValueException('Invalid route name'); |
|
135 | - } |
|
136 | - [$controller, $action] = $split; |
|
137 | - |
|
138 | - $controllerName = $this->buildControllerName($controller); |
|
139 | - $actionName = $this->buildActionName($action); |
|
140 | - |
|
141 | - $routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix; |
|
142 | - |
|
143 | - $router = $this->router->create($routeName, $url) |
|
144 | - ->method($verb); |
|
145 | - |
|
146 | - // optionally register requirements for route. This is used to |
|
147 | - // tell the route parser how url parameters should be matched |
|
148 | - if (array_key_exists('requirements', $route)) { |
|
149 | - $router->requirements($route['requirements']); |
|
150 | - } |
|
151 | - |
|
152 | - // optionally register defaults for route. This is used to |
|
153 | - // tell the route parser how url parameters should be default valued |
|
154 | - $defaults = []; |
|
155 | - if (array_key_exists('defaults', $route)) { |
|
156 | - $defaults = $route['defaults']; |
|
157 | - } |
|
158 | - |
|
159 | - $defaults['caller'] = [$this->appName, $controllerName, $actionName]; |
|
160 | - $router->defaults($defaults); |
|
161 | - } |
|
162 | - |
|
163 | - /** |
|
164 | - * For a given name and url restful OCS routes are created: |
|
165 | - * - index |
|
166 | - * - show |
|
167 | - * - create |
|
168 | - * - update |
|
169 | - * - destroy |
|
170 | - * |
|
171 | - * @param array $routes |
|
172 | - */ |
|
173 | - private function processOCSResources(array $routes): void { |
|
174 | - $this->processResources($routes['ocs-resources'] ?? [], 'ocs.'); |
|
175 | - } |
|
176 | - |
|
177 | - /** |
|
178 | - * For a given name and url restful routes are created: |
|
179 | - * - index |
|
180 | - * - show |
|
181 | - * - create |
|
182 | - * - update |
|
183 | - * - destroy |
|
184 | - * |
|
185 | - * @param array $routes |
|
186 | - */ |
|
187 | - private function processIndexResources(array $routes): void { |
|
188 | - $this->processResources($routes['resources'] ?? []); |
|
189 | - } |
|
190 | - |
|
191 | - /** |
|
192 | - * For a given name and url restful routes are created: |
|
193 | - * - index |
|
194 | - * - show |
|
195 | - * - create |
|
196 | - * - update |
|
197 | - * - destroy |
|
198 | - * |
|
199 | - * @param array $resources |
|
200 | - * @param string $routeNamePrefix |
|
201 | - */ |
|
202 | - protected function processResources(array $resources, string $routeNamePrefix = ''): void { |
|
203 | - // declaration of all restful actions |
|
204 | - $actions = [ |
|
205 | - ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], |
|
206 | - ['name' => 'show', 'verb' => 'GET'], |
|
207 | - ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], |
|
208 | - ['name' => 'update', 'verb' => 'PUT'], |
|
209 | - ['name' => 'destroy', 'verb' => 'DELETE'], |
|
210 | - ]; |
|
211 | - |
|
212 | - foreach ($resources as $resource => $config) { |
|
213 | - $root = $this->buildRootPrefix($config, $routeNamePrefix); |
|
214 | - |
|
215 | - // the url parameter used as id to the resource |
|
216 | - foreach ($actions as $action) { |
|
217 | - $url = $root . '/' . ltrim($config['url'], '/'); |
|
218 | - $method = $action['name']; |
|
219 | - |
|
220 | - $verb = strtoupper($action['verb'] ?? 'GET'); |
|
221 | - $collectionAction = $action['on-collection'] ?? false; |
|
222 | - if (!$collectionAction) { |
|
223 | - $url .= '/{id}'; |
|
224 | - } |
|
225 | - if (isset($action['url-postfix'])) { |
|
226 | - $url .= '/' . $action['url-postfix']; |
|
227 | - } |
|
228 | - |
|
229 | - $controller = $resource; |
|
230 | - |
|
231 | - $controllerName = $this->buildControllerName($controller); |
|
232 | - $actionName = $this->buildActionName($method); |
|
233 | - |
|
234 | - $routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method; |
|
235 | - |
|
236 | - $route = $this->router->create($routeName, $url) |
|
237 | - ->method($verb); |
|
238 | - |
|
239 | - $route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]); |
|
240 | - } |
|
241 | - } |
|
242 | - } |
|
243 | - |
|
244 | - private function buildRootPrefix(array $route, string $routeNamePrefix): string { |
|
245 | - $defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName; |
|
246 | - $root = $route['root'] ?? $defaultRoot; |
|
247 | - |
|
248 | - if ($routeNamePrefix !== '') { |
|
249 | - // In OCS all apps are whitelisted |
|
250 | - return $root; |
|
251 | - } |
|
252 | - |
|
253 | - if (!\in_array($this->appName, $this->rootUrlApps, true)) { |
|
254 | - // Only allow root URLS for some apps |
|
255 | - return $defaultRoot; |
|
256 | - } |
|
257 | - |
|
258 | - return $root; |
|
259 | - } |
|
260 | - |
|
261 | - /** |
|
262 | - * Based on a given route name the controller name is generated |
|
263 | - * @param string $controller |
|
264 | - * @return string |
|
265 | - */ |
|
266 | - private function buildControllerName(string $controller): string { |
|
267 | - if (!isset($this->controllerNameCache[$controller])) { |
|
268 | - $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; |
|
269 | - } |
|
270 | - return $this->controllerNameCache[$controller]; |
|
271 | - } |
|
272 | - |
|
273 | - /** |
|
274 | - * Based on the action part of the route name the controller method name is generated |
|
275 | - * @param string $action |
|
276 | - * @return string |
|
277 | - */ |
|
278 | - private function buildActionName(string $action): string { |
|
279 | - return $this->underScoreToCamelCase($action); |
|
280 | - } |
|
281 | - |
|
282 | - /** |
|
283 | - * Underscored strings are converted to camel case strings |
|
284 | - * @param string $str |
|
285 | - * @return string |
|
286 | - */ |
|
287 | - private function underScoreToCamelCase(string $str): string { |
|
288 | - $pattern = '/_[a-z]?/'; |
|
289 | - return preg_replace_callback( |
|
290 | - $pattern, |
|
291 | - function ($matches) { |
|
292 | - return strtoupper(ltrim($matches[0], '_')); |
|
293 | - }, |
|
294 | - $str); |
|
295 | - } |
|
93 | + $oldCollection = $this->router->getCurrentCollection(); |
|
94 | + $this->router->useCollection($oldCollection . '.ocs'); |
|
95 | + |
|
96 | + // parse ocs simple routes |
|
97 | + $this->processOCS($this->routes); |
|
98 | + |
|
99 | + // parse ocs simple routes |
|
100 | + $this->processOCSResources($this->routes); |
|
101 | + |
|
102 | + $this->router->useCollection($oldCollection); |
|
103 | + } |
|
104 | + |
|
105 | + private function processOCS(array $routes): void { |
|
106 | + $ocsRoutes = $routes['ocs'] ?? []; |
|
107 | + foreach ($ocsRoutes as $ocsRoute) { |
|
108 | + $this->processRoute($ocsRoute, 'ocs.'); |
|
109 | + } |
|
110 | + } |
|
111 | + |
|
112 | + /** |
|
113 | + * Creates one route base on the give configuration |
|
114 | + * @param array $routes |
|
115 | + * @throws \UnexpectedValueException |
|
116 | + */ |
|
117 | + private function processIndexRoutes(array $routes): void { |
|
118 | + $simpleRoutes = $routes['routes'] ?? []; |
|
119 | + foreach ($simpleRoutes as $simpleRoute) { |
|
120 | + $this->processRoute($simpleRoute); |
|
121 | + } |
|
122 | + } |
|
123 | + |
|
124 | + protected function processRoute(array $route, string $routeNamePrefix = ''): void { |
|
125 | + $name = $route['name']; |
|
126 | + $postfix = $route['postfix'] ?? ''; |
|
127 | + $root = $this->buildRootPrefix($route, $routeNamePrefix); |
|
128 | + |
|
129 | + $url = $root . '/' . ltrim($route['url'], '/'); |
|
130 | + $verb = strtoupper($route['verb'] ?? 'GET'); |
|
131 | + |
|
132 | + $split = explode('#', $name, 2); |
|
133 | + if (count($split) !== 2) { |
|
134 | + throw new \UnexpectedValueException('Invalid route name'); |
|
135 | + } |
|
136 | + [$controller, $action] = $split; |
|
137 | + |
|
138 | + $controllerName = $this->buildControllerName($controller); |
|
139 | + $actionName = $this->buildActionName($action); |
|
140 | + |
|
141 | + $routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix; |
|
142 | + |
|
143 | + $router = $this->router->create($routeName, $url) |
|
144 | + ->method($verb); |
|
145 | + |
|
146 | + // optionally register requirements for route. This is used to |
|
147 | + // tell the route parser how url parameters should be matched |
|
148 | + if (array_key_exists('requirements', $route)) { |
|
149 | + $router->requirements($route['requirements']); |
|
150 | + } |
|
151 | + |
|
152 | + // optionally register defaults for route. This is used to |
|
153 | + // tell the route parser how url parameters should be default valued |
|
154 | + $defaults = []; |
|
155 | + if (array_key_exists('defaults', $route)) { |
|
156 | + $defaults = $route['defaults']; |
|
157 | + } |
|
158 | + |
|
159 | + $defaults['caller'] = [$this->appName, $controllerName, $actionName]; |
|
160 | + $router->defaults($defaults); |
|
161 | + } |
|
162 | + |
|
163 | + /** |
|
164 | + * For a given name and url restful OCS routes are created: |
|
165 | + * - index |
|
166 | + * - show |
|
167 | + * - create |
|
168 | + * - update |
|
169 | + * - destroy |
|
170 | + * |
|
171 | + * @param array $routes |
|
172 | + */ |
|
173 | + private function processOCSResources(array $routes): void { |
|
174 | + $this->processResources($routes['ocs-resources'] ?? [], 'ocs.'); |
|
175 | + } |
|
176 | + |
|
177 | + /** |
|
178 | + * For a given name and url restful routes are created: |
|
179 | + * - index |
|
180 | + * - show |
|
181 | + * - create |
|
182 | + * - update |
|
183 | + * - destroy |
|
184 | + * |
|
185 | + * @param array $routes |
|
186 | + */ |
|
187 | + private function processIndexResources(array $routes): void { |
|
188 | + $this->processResources($routes['resources'] ?? []); |
|
189 | + } |
|
190 | + |
|
191 | + /** |
|
192 | + * For a given name and url restful routes are created: |
|
193 | + * - index |
|
194 | + * - show |
|
195 | + * - create |
|
196 | + * - update |
|
197 | + * - destroy |
|
198 | + * |
|
199 | + * @param array $resources |
|
200 | + * @param string $routeNamePrefix |
|
201 | + */ |
|
202 | + protected function processResources(array $resources, string $routeNamePrefix = ''): void { |
|
203 | + // declaration of all restful actions |
|
204 | + $actions = [ |
|
205 | + ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], |
|
206 | + ['name' => 'show', 'verb' => 'GET'], |
|
207 | + ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], |
|
208 | + ['name' => 'update', 'verb' => 'PUT'], |
|
209 | + ['name' => 'destroy', 'verb' => 'DELETE'], |
|
210 | + ]; |
|
211 | + |
|
212 | + foreach ($resources as $resource => $config) { |
|
213 | + $root = $this->buildRootPrefix($config, $routeNamePrefix); |
|
214 | + |
|
215 | + // the url parameter used as id to the resource |
|
216 | + foreach ($actions as $action) { |
|
217 | + $url = $root . '/' . ltrim($config['url'], '/'); |
|
218 | + $method = $action['name']; |
|
219 | + |
|
220 | + $verb = strtoupper($action['verb'] ?? 'GET'); |
|
221 | + $collectionAction = $action['on-collection'] ?? false; |
|
222 | + if (!$collectionAction) { |
|
223 | + $url .= '/{id}'; |
|
224 | + } |
|
225 | + if (isset($action['url-postfix'])) { |
|
226 | + $url .= '/' . $action['url-postfix']; |
|
227 | + } |
|
228 | + |
|
229 | + $controller = $resource; |
|
230 | + |
|
231 | + $controllerName = $this->buildControllerName($controller); |
|
232 | + $actionName = $this->buildActionName($method); |
|
233 | + |
|
234 | + $routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method; |
|
235 | + |
|
236 | + $route = $this->router->create($routeName, $url) |
|
237 | + ->method($verb); |
|
238 | + |
|
239 | + $route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]); |
|
240 | + } |
|
241 | + } |
|
242 | + } |
|
243 | + |
|
244 | + private function buildRootPrefix(array $route, string $routeNamePrefix): string { |
|
245 | + $defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName; |
|
246 | + $root = $route['root'] ?? $defaultRoot; |
|
247 | + |
|
248 | + if ($routeNamePrefix !== '') { |
|
249 | + // In OCS all apps are whitelisted |
|
250 | + return $root; |
|
251 | + } |
|
252 | + |
|
253 | + if (!\in_array($this->appName, $this->rootUrlApps, true)) { |
|
254 | + // Only allow root URLS for some apps |
|
255 | + return $defaultRoot; |
|
256 | + } |
|
257 | + |
|
258 | + return $root; |
|
259 | + } |
|
260 | + |
|
261 | + /** |
|
262 | + * Based on a given route name the controller name is generated |
|
263 | + * @param string $controller |
|
264 | + * @return string |
|
265 | + */ |
|
266 | + private function buildControllerName(string $controller): string { |
|
267 | + if (!isset($this->controllerNameCache[$controller])) { |
|
268 | + $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; |
|
269 | + } |
|
270 | + return $this->controllerNameCache[$controller]; |
|
271 | + } |
|
272 | + |
|
273 | + /** |
|
274 | + * Based on the action part of the route name the controller method name is generated |
|
275 | + * @param string $action |
|
276 | + * @return string |
|
277 | + */ |
|
278 | + private function buildActionName(string $action): string { |
|
279 | + return $this->underScoreToCamelCase($action); |
|
280 | + } |
|
281 | + |
|
282 | + /** |
|
283 | + * Underscored strings are converted to camel case strings |
|
284 | + * @param string $str |
|
285 | + * @return string |
|
286 | + */ |
|
287 | + private function underScoreToCamelCase(string $str): string { |
|
288 | + $pattern = '/_[a-z]?/'; |
|
289 | + return preg_replace_callback( |
|
290 | + $pattern, |
|
291 | + function ($matches) { |
|
292 | + return strtoupper(ltrim($matches[0], '_')); |
|
293 | + }, |
|
294 | + $str); |
|
295 | + } |
|
296 | 296 | } |
@@ -29,235 +29,235 @@ |
||
29 | 29 | use Symfony\Component\Routing\RouteCollection; |
30 | 30 | |
31 | 31 | class RouteParser { |
32 | - /** @var string[] */ |
|
33 | - private $controllerNameCache = []; |
|
34 | - |
|
35 | - private const rootUrlApps = [ |
|
36 | - 'cloud_federation_api', |
|
37 | - 'core', |
|
38 | - 'files_sharing', |
|
39 | - 'files', |
|
40 | - 'settings', |
|
41 | - 'spreed', |
|
42 | - ]; |
|
43 | - |
|
44 | - public function parseDefaultRoutes(array $routes, string $appName): RouteCollection { |
|
45 | - $collection = $this->processIndexRoutes($routes, $appName); |
|
46 | - $collection->addCollection($this->processIndexResources($routes, $appName)); |
|
47 | - |
|
48 | - return $collection; |
|
49 | - } |
|
50 | - |
|
51 | - public function parseOCSRoutes(array $routes, string $appName): RouteCollection { |
|
52 | - $collection = $this->processOCS($routes, $appName); |
|
53 | - $collection->addCollection($this->processOCSResources($routes, $appName)); |
|
54 | - |
|
55 | - return $collection; |
|
56 | - } |
|
57 | - |
|
58 | - private function processOCS(array $routes, string $appName): RouteCollection { |
|
59 | - $collection = new RouteCollection(); |
|
60 | - $ocsRoutes = $routes['ocs'] ?? []; |
|
61 | - foreach ($ocsRoutes as $ocsRoute) { |
|
62 | - $result = $this->processRoute($ocsRoute, $appName, 'ocs.'); |
|
63 | - |
|
64 | - $collection->add($result[0], $result[1]); |
|
65 | - } |
|
66 | - |
|
67 | - return $collection; |
|
68 | - } |
|
69 | - |
|
70 | - /** |
|
71 | - * Creates one route base on the give configuration |
|
72 | - * @param array $routes |
|
73 | - * @throws \UnexpectedValueException |
|
74 | - */ |
|
75 | - private function processIndexRoutes(array $routes, string $appName): RouteCollection { |
|
76 | - $collection = new RouteCollection(); |
|
77 | - $simpleRoutes = $routes['routes'] ?? []; |
|
78 | - foreach ($simpleRoutes as $simpleRoute) { |
|
79 | - $result = $this->processRoute($simpleRoute, $appName); |
|
80 | - |
|
81 | - $collection->add($result[0], $result[1]); |
|
82 | - } |
|
83 | - |
|
84 | - return $collection; |
|
85 | - } |
|
86 | - |
|
87 | - private function processRoute(array $route, string $appName, string $routeNamePrefix = ''): array { |
|
88 | - $name = $route['name']; |
|
89 | - $postfix = $route['postfix'] ?? ''; |
|
90 | - $root = $this->buildRootPrefix($route, $appName, $routeNamePrefix); |
|
91 | - |
|
92 | - $url = $root . '/' . ltrim($route['url'], '/'); |
|
93 | - $verb = strtoupper($route['verb'] ?? 'GET'); |
|
94 | - |
|
95 | - $split = explode('#', $name, 2); |
|
96 | - if (count($split) !== 2) { |
|
97 | - throw new \UnexpectedValueException('Invalid route name'); |
|
98 | - } |
|
99 | - [$controller, $action] = $split; |
|
100 | - |
|
101 | - $controllerName = $this->buildControllerName($controller); |
|
102 | - $actionName = $this->buildActionName($action); |
|
103 | - |
|
104 | - $routeName = $routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix; |
|
105 | - |
|
106 | - $routeObject = new Route($url); |
|
107 | - $routeObject->method($verb); |
|
108 | - |
|
109 | - // optionally register requirements for route. This is used to |
|
110 | - // tell the route parser how url parameters should be matched |
|
111 | - if (array_key_exists('requirements', $route)) { |
|
112 | - $routeObject->requirements($route['requirements']); |
|
113 | - } |
|
114 | - |
|
115 | - // optionally register defaults for route. This is used to |
|
116 | - // tell the route parser how url parameters should be default valued |
|
117 | - $defaults = []; |
|
118 | - if (array_key_exists('defaults', $route)) { |
|
119 | - $defaults = $route['defaults']; |
|
120 | - } |
|
121 | - |
|
122 | - $defaults['caller'] = [$appName, $controllerName, $actionName]; |
|
123 | - $routeObject->defaults($defaults); |
|
124 | - |
|
125 | - return [$routeName, $routeObject]; |
|
126 | - } |
|
127 | - |
|
128 | - /** |
|
129 | - * For a given name and url restful OCS routes are created: |
|
130 | - * - index |
|
131 | - * - show |
|
132 | - * - create |
|
133 | - * - update |
|
134 | - * - destroy |
|
135 | - * |
|
136 | - * @param array $routes |
|
137 | - */ |
|
138 | - private function processOCSResources(array $routes, string $appName): RouteCollection { |
|
139 | - return $this->processResources($routes['ocs-resources'] ?? [], $appName, 'ocs.'); |
|
140 | - } |
|
141 | - |
|
142 | - /** |
|
143 | - * For a given name and url restful routes are created: |
|
144 | - * - index |
|
145 | - * - show |
|
146 | - * - create |
|
147 | - * - update |
|
148 | - * - destroy |
|
149 | - * |
|
150 | - * @param array $routes |
|
151 | - */ |
|
152 | - private function processIndexResources(array $routes, string $appName): RouteCollection { |
|
153 | - return $this->processResources($routes['resources'] ?? [], $appName); |
|
154 | - } |
|
155 | - |
|
156 | - /** |
|
157 | - * For a given name and url restful routes are created: |
|
158 | - * - index |
|
159 | - * - show |
|
160 | - * - create |
|
161 | - * - update |
|
162 | - * - destroy |
|
163 | - * |
|
164 | - * @param array $resources |
|
165 | - * @param string $routeNamePrefix |
|
166 | - */ |
|
167 | - private function processResources(array $resources, string $appName, string $routeNamePrefix = ''): RouteCollection { |
|
168 | - // declaration of all restful actions |
|
169 | - $actions = [ |
|
170 | - ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], |
|
171 | - ['name' => 'show', 'verb' => 'GET'], |
|
172 | - ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], |
|
173 | - ['name' => 'update', 'verb' => 'PUT'], |
|
174 | - ['name' => 'destroy', 'verb' => 'DELETE'], |
|
175 | - ]; |
|
176 | - |
|
177 | - $collection = new RouteCollection(); |
|
178 | - foreach ($resources as $resource => $config) { |
|
179 | - $root = $this->buildRootPrefix($config, $appName, $routeNamePrefix); |
|
180 | - |
|
181 | - // the url parameter used as id to the resource |
|
182 | - foreach ($actions as $action) { |
|
183 | - $url = $root . '/' . ltrim($config['url'], '/'); |
|
184 | - $method = $action['name']; |
|
185 | - |
|
186 | - $verb = strtoupper($action['verb'] ?? 'GET'); |
|
187 | - $collectionAction = $action['on-collection'] ?? false; |
|
188 | - if (!$collectionAction) { |
|
189 | - $url .= '/{id}'; |
|
190 | - } |
|
191 | - |
|
192 | - $controller = $resource; |
|
193 | - |
|
194 | - $controllerName = $this->buildControllerName($controller); |
|
195 | - $actionName = $this->buildActionName($method); |
|
196 | - |
|
197 | - $routeName = $routeNamePrefix . $appName . '.' . strtolower($resource) . '.' . $method; |
|
198 | - |
|
199 | - $route = new Route($url); |
|
200 | - $route->method($verb); |
|
201 | - |
|
202 | - $route->defaults(['caller' => [$appName, $controllerName, $actionName]]); |
|
203 | - |
|
204 | - $collection->add($routeName, $route); |
|
205 | - } |
|
206 | - } |
|
207 | - |
|
208 | - return $collection; |
|
209 | - } |
|
210 | - |
|
211 | - private function buildRootPrefix(array $route, string $appName, string $routeNamePrefix): string { |
|
212 | - $defaultRoot = $appName === 'core' ? '' : '/apps/' . $appName; |
|
213 | - $root = $route['root'] ?? $defaultRoot; |
|
214 | - |
|
215 | - if ($routeNamePrefix !== '') { |
|
216 | - // In OCS all apps are whitelisted |
|
217 | - return $root; |
|
218 | - } |
|
219 | - |
|
220 | - if (!\in_array($appName, self::rootUrlApps, true)) { |
|
221 | - // Only allow root URLS for some apps |
|
222 | - return $defaultRoot; |
|
223 | - } |
|
224 | - |
|
225 | - return $root; |
|
226 | - } |
|
227 | - |
|
228 | - /** |
|
229 | - * Based on a given route name the controller name is generated |
|
230 | - * @param string $controller |
|
231 | - * @return string |
|
232 | - */ |
|
233 | - private function buildControllerName(string $controller): string { |
|
234 | - if (!isset($this->controllerNameCache[$controller])) { |
|
235 | - $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; |
|
236 | - } |
|
237 | - return $this->controllerNameCache[$controller]; |
|
238 | - } |
|
239 | - |
|
240 | - /** |
|
241 | - * Based on the action part of the route name the controller method name is generated |
|
242 | - * @param string $action |
|
243 | - * @return string |
|
244 | - */ |
|
245 | - private function buildActionName(string $action): string { |
|
246 | - return $this->underScoreToCamelCase($action); |
|
247 | - } |
|
248 | - |
|
249 | - /** |
|
250 | - * Underscored strings are converted to camel case strings |
|
251 | - * @param string $str |
|
252 | - * @return string |
|
253 | - */ |
|
254 | - private function underScoreToCamelCase(string $str): string { |
|
255 | - $pattern = '/_[a-z]?/'; |
|
256 | - return preg_replace_callback( |
|
257 | - $pattern, |
|
258 | - function ($matches) { |
|
259 | - return strtoupper(ltrim($matches[0], '_')); |
|
260 | - }, |
|
261 | - $str); |
|
262 | - } |
|
32 | + /** @var string[] */ |
|
33 | + private $controllerNameCache = []; |
|
34 | + |
|
35 | + private const rootUrlApps = [ |
|
36 | + 'cloud_federation_api', |
|
37 | + 'core', |
|
38 | + 'files_sharing', |
|
39 | + 'files', |
|
40 | + 'settings', |
|
41 | + 'spreed', |
|
42 | + ]; |
|
43 | + |
|
44 | + public function parseDefaultRoutes(array $routes, string $appName): RouteCollection { |
|
45 | + $collection = $this->processIndexRoutes($routes, $appName); |
|
46 | + $collection->addCollection($this->processIndexResources($routes, $appName)); |
|
47 | + |
|
48 | + return $collection; |
|
49 | + } |
|
50 | + |
|
51 | + public function parseOCSRoutes(array $routes, string $appName): RouteCollection { |
|
52 | + $collection = $this->processOCS($routes, $appName); |
|
53 | + $collection->addCollection($this->processOCSResources($routes, $appName)); |
|
54 | + |
|
55 | + return $collection; |
|
56 | + } |
|
57 | + |
|
58 | + private function processOCS(array $routes, string $appName): RouteCollection { |
|
59 | + $collection = new RouteCollection(); |
|
60 | + $ocsRoutes = $routes['ocs'] ?? []; |
|
61 | + foreach ($ocsRoutes as $ocsRoute) { |
|
62 | + $result = $this->processRoute($ocsRoute, $appName, 'ocs.'); |
|
63 | + |
|
64 | + $collection->add($result[0], $result[1]); |
|
65 | + } |
|
66 | + |
|
67 | + return $collection; |
|
68 | + } |
|
69 | + |
|
70 | + /** |
|
71 | + * Creates one route base on the give configuration |
|
72 | + * @param array $routes |
|
73 | + * @throws \UnexpectedValueException |
|
74 | + */ |
|
75 | + private function processIndexRoutes(array $routes, string $appName): RouteCollection { |
|
76 | + $collection = new RouteCollection(); |
|
77 | + $simpleRoutes = $routes['routes'] ?? []; |
|
78 | + foreach ($simpleRoutes as $simpleRoute) { |
|
79 | + $result = $this->processRoute($simpleRoute, $appName); |
|
80 | + |
|
81 | + $collection->add($result[0], $result[1]); |
|
82 | + } |
|
83 | + |
|
84 | + return $collection; |
|
85 | + } |
|
86 | + |
|
87 | + private function processRoute(array $route, string $appName, string $routeNamePrefix = ''): array { |
|
88 | + $name = $route['name']; |
|
89 | + $postfix = $route['postfix'] ?? ''; |
|
90 | + $root = $this->buildRootPrefix($route, $appName, $routeNamePrefix); |
|
91 | + |
|
92 | + $url = $root . '/' . ltrim($route['url'], '/'); |
|
93 | + $verb = strtoupper($route['verb'] ?? 'GET'); |
|
94 | + |
|
95 | + $split = explode('#', $name, 2); |
|
96 | + if (count($split) !== 2) { |
|
97 | + throw new \UnexpectedValueException('Invalid route name'); |
|
98 | + } |
|
99 | + [$controller, $action] = $split; |
|
100 | + |
|
101 | + $controllerName = $this->buildControllerName($controller); |
|
102 | + $actionName = $this->buildActionName($action); |
|
103 | + |
|
104 | + $routeName = $routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix; |
|
105 | + |
|
106 | + $routeObject = new Route($url); |
|
107 | + $routeObject->method($verb); |
|
108 | + |
|
109 | + // optionally register requirements for route. This is used to |
|
110 | + // tell the route parser how url parameters should be matched |
|
111 | + if (array_key_exists('requirements', $route)) { |
|
112 | + $routeObject->requirements($route['requirements']); |
|
113 | + } |
|
114 | + |
|
115 | + // optionally register defaults for route. This is used to |
|
116 | + // tell the route parser how url parameters should be default valued |
|
117 | + $defaults = []; |
|
118 | + if (array_key_exists('defaults', $route)) { |
|
119 | + $defaults = $route['defaults']; |
|
120 | + } |
|
121 | + |
|
122 | + $defaults['caller'] = [$appName, $controllerName, $actionName]; |
|
123 | + $routeObject->defaults($defaults); |
|
124 | + |
|
125 | + return [$routeName, $routeObject]; |
|
126 | + } |
|
127 | + |
|
128 | + /** |
|
129 | + * For a given name and url restful OCS routes are created: |
|
130 | + * - index |
|
131 | + * - show |
|
132 | + * - create |
|
133 | + * - update |
|
134 | + * - destroy |
|
135 | + * |
|
136 | + * @param array $routes |
|
137 | + */ |
|
138 | + private function processOCSResources(array $routes, string $appName): RouteCollection { |
|
139 | + return $this->processResources($routes['ocs-resources'] ?? [], $appName, 'ocs.'); |
|
140 | + } |
|
141 | + |
|
142 | + /** |
|
143 | + * For a given name and url restful routes are created: |
|
144 | + * - index |
|
145 | + * - show |
|
146 | + * - create |
|
147 | + * - update |
|
148 | + * - destroy |
|
149 | + * |
|
150 | + * @param array $routes |
|
151 | + */ |
|
152 | + private function processIndexResources(array $routes, string $appName): RouteCollection { |
|
153 | + return $this->processResources($routes['resources'] ?? [], $appName); |
|
154 | + } |
|
155 | + |
|
156 | + /** |
|
157 | + * For a given name and url restful routes are created: |
|
158 | + * - index |
|
159 | + * - show |
|
160 | + * - create |
|
161 | + * - update |
|
162 | + * - destroy |
|
163 | + * |
|
164 | + * @param array $resources |
|
165 | + * @param string $routeNamePrefix |
|
166 | + */ |
|
167 | + private function processResources(array $resources, string $appName, string $routeNamePrefix = ''): RouteCollection { |
|
168 | + // declaration of all restful actions |
|
169 | + $actions = [ |
|
170 | + ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], |
|
171 | + ['name' => 'show', 'verb' => 'GET'], |
|
172 | + ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], |
|
173 | + ['name' => 'update', 'verb' => 'PUT'], |
|
174 | + ['name' => 'destroy', 'verb' => 'DELETE'], |
|
175 | + ]; |
|
176 | + |
|
177 | + $collection = new RouteCollection(); |
|
178 | + foreach ($resources as $resource => $config) { |
|
179 | + $root = $this->buildRootPrefix($config, $appName, $routeNamePrefix); |
|
180 | + |
|
181 | + // the url parameter used as id to the resource |
|
182 | + foreach ($actions as $action) { |
|
183 | + $url = $root . '/' . ltrim($config['url'], '/'); |
|
184 | + $method = $action['name']; |
|
185 | + |
|
186 | + $verb = strtoupper($action['verb'] ?? 'GET'); |
|
187 | + $collectionAction = $action['on-collection'] ?? false; |
|
188 | + if (!$collectionAction) { |
|
189 | + $url .= '/{id}'; |
|
190 | + } |
|
191 | + |
|
192 | + $controller = $resource; |
|
193 | + |
|
194 | + $controllerName = $this->buildControllerName($controller); |
|
195 | + $actionName = $this->buildActionName($method); |
|
196 | + |
|
197 | + $routeName = $routeNamePrefix . $appName . '.' . strtolower($resource) . '.' . $method; |
|
198 | + |
|
199 | + $route = new Route($url); |
|
200 | + $route->method($verb); |
|
201 | + |
|
202 | + $route->defaults(['caller' => [$appName, $controllerName, $actionName]]); |
|
203 | + |
|
204 | + $collection->add($routeName, $route); |
|
205 | + } |
|
206 | + } |
|
207 | + |
|
208 | + return $collection; |
|
209 | + } |
|
210 | + |
|
211 | + private function buildRootPrefix(array $route, string $appName, string $routeNamePrefix): string { |
|
212 | + $defaultRoot = $appName === 'core' ? '' : '/apps/' . $appName; |
|
213 | + $root = $route['root'] ?? $defaultRoot; |
|
214 | + |
|
215 | + if ($routeNamePrefix !== '') { |
|
216 | + // In OCS all apps are whitelisted |
|
217 | + return $root; |
|
218 | + } |
|
219 | + |
|
220 | + if (!\in_array($appName, self::rootUrlApps, true)) { |
|
221 | + // Only allow root URLS for some apps |
|
222 | + return $defaultRoot; |
|
223 | + } |
|
224 | + |
|
225 | + return $root; |
|
226 | + } |
|
227 | + |
|
228 | + /** |
|
229 | + * Based on a given route name the controller name is generated |
|
230 | + * @param string $controller |
|
231 | + * @return string |
|
232 | + */ |
|
233 | + private function buildControllerName(string $controller): string { |
|
234 | + if (!isset($this->controllerNameCache[$controller])) { |
|
235 | + $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; |
|
236 | + } |
|
237 | + return $this->controllerNameCache[$controller]; |
|
238 | + } |
|
239 | + |
|
240 | + /** |
|
241 | + * Based on the action part of the route name the controller method name is generated |
|
242 | + * @param string $action |
|
243 | + * @return string |
|
244 | + */ |
|
245 | + private function buildActionName(string $action): string { |
|
246 | + return $this->underScoreToCamelCase($action); |
|
247 | + } |
|
248 | + |
|
249 | + /** |
|
250 | + * Underscored strings are converted to camel case strings |
|
251 | + * @param string $str |
|
252 | + * @return string |
|
253 | + */ |
|
254 | + private function underScoreToCamelCase(string $str): string { |
|
255 | + $pattern = '/_[a-z]?/'; |
|
256 | + return preg_replace_callback( |
|
257 | + $pattern, |
|
258 | + function ($matches) { |
|
259 | + return strtoupper(ltrim($matches[0], '_')); |
|
260 | + }, |
|
261 | + $str); |
|
262 | + } |
|
263 | 263 | } |
@@ -40,103 +40,103 @@ |
||
40 | 40 | * Reads and parses annotations from doc comments |
41 | 41 | */ |
42 | 42 | class ControllerMethodReflector implements IControllerMethodReflector { |
43 | - public $annotations = []; |
|
44 | - private $types = []; |
|
45 | - private $parameters = []; |
|
46 | - |
|
47 | - /** |
|
48 | - * @param object $object an object or classname |
|
49 | - * @param string $method the method which we want to inspect |
|
50 | - */ |
|
51 | - public function reflect($object, string $method) { |
|
52 | - $reflection = new \ReflectionMethod($object, $method); |
|
53 | - $docs = $reflection->getDocComment(); |
|
54 | - |
|
55 | - if ($docs !== false) { |
|
56 | - // extract everything prefixed by @ and first letter uppercase |
|
57 | - preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches); |
|
58 | - foreach ($matches['annotation'] as $key => $annontation) { |
|
59 | - $annontation = strtolower($annontation); |
|
60 | - $annotationValue = $matches['parameter'][$key]; |
|
61 | - if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') { |
|
62 | - $cutString = substr($annotationValue, 1, -1); |
|
63 | - $cutString = str_replace(' ', '', $cutString); |
|
64 | - $splittedArray = explode(',', $cutString); |
|
65 | - foreach ($splittedArray as $annotationValues) { |
|
66 | - [$key, $value] = explode('=', $annotationValues); |
|
67 | - $this->annotations[$annontation][$key] = $value; |
|
68 | - } |
|
69 | - continue; |
|
70 | - } |
|
71 | - |
|
72 | - $this->annotations[$annontation] = [$annotationValue]; |
|
73 | - } |
|
74 | - |
|
75 | - // extract type parameter information |
|
76 | - preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches); |
|
77 | - $this->types = array_combine($matches['var'], $matches['type']); |
|
78 | - } |
|
79 | - |
|
80 | - foreach ($reflection->getParameters() as $param) { |
|
81 | - // extract type information from PHP 7 scalar types and prefer them over phpdoc annotations |
|
82 | - $type = $param->getType(); |
|
83 | - if ($type instanceof \ReflectionNamedType) { |
|
84 | - $this->types[$param->getName()] = $type->getName(); |
|
85 | - } |
|
86 | - |
|
87 | - $default = null; |
|
88 | - if ($param->isOptional()) { |
|
89 | - $default = $param->getDefaultValue(); |
|
90 | - } |
|
91 | - $this->parameters[$param->name] = $default; |
|
92 | - } |
|
93 | - } |
|
94 | - |
|
95 | - /** |
|
96 | - * Inspects the PHPDoc parameters for types |
|
97 | - * @param string $parameter the parameter whose type comments should be |
|
98 | - * parsed |
|
99 | - * @return string|null type in the type parameters (@param int $something) |
|
100 | - * would return int or null if not existing |
|
101 | - */ |
|
102 | - public function getType(string $parameter) { |
|
103 | - if (array_key_exists($parameter, $this->types)) { |
|
104 | - return $this->types[$parameter]; |
|
105 | - } |
|
106 | - |
|
107 | - return null; |
|
108 | - } |
|
109 | - |
|
110 | - /** |
|
111 | - * @return array the arguments of the method with key => default value |
|
112 | - */ |
|
113 | - public function getParameters(): array { |
|
114 | - return $this->parameters; |
|
115 | - } |
|
116 | - |
|
117 | - /** |
|
118 | - * Check if a method contains an annotation |
|
119 | - * @param string $name the name of the annotation |
|
120 | - * @return bool true if the annotation is found |
|
121 | - */ |
|
122 | - public function hasAnnotation(string $name): bool { |
|
123 | - $name = strtolower($name); |
|
124 | - return array_key_exists($name, $this->annotations); |
|
125 | - } |
|
126 | - |
|
127 | - /** |
|
128 | - * Get optional annotation parameter by key |
|
129 | - * |
|
130 | - * @param string $name the name of the annotation |
|
131 | - * @param string $key the string of the annotation |
|
132 | - * @return string |
|
133 | - */ |
|
134 | - public function getAnnotationParameter(string $name, string $key): string { |
|
135 | - $name = strtolower($name); |
|
136 | - if (isset($this->annotations[$name][$key])) { |
|
137 | - return $this->annotations[$name][$key]; |
|
138 | - } |
|
139 | - |
|
140 | - return ''; |
|
141 | - } |
|
43 | + public $annotations = []; |
|
44 | + private $types = []; |
|
45 | + private $parameters = []; |
|
46 | + |
|
47 | + /** |
|
48 | + * @param object $object an object or classname |
|
49 | + * @param string $method the method which we want to inspect |
|
50 | + */ |
|
51 | + public function reflect($object, string $method) { |
|
52 | + $reflection = new \ReflectionMethod($object, $method); |
|
53 | + $docs = $reflection->getDocComment(); |
|
54 | + |
|
55 | + if ($docs !== false) { |
|
56 | + // extract everything prefixed by @ and first letter uppercase |
|
57 | + preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches); |
|
58 | + foreach ($matches['annotation'] as $key => $annontation) { |
|
59 | + $annontation = strtolower($annontation); |
|
60 | + $annotationValue = $matches['parameter'][$key]; |
|
61 | + if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') { |
|
62 | + $cutString = substr($annotationValue, 1, -1); |
|
63 | + $cutString = str_replace(' ', '', $cutString); |
|
64 | + $splittedArray = explode(',', $cutString); |
|
65 | + foreach ($splittedArray as $annotationValues) { |
|
66 | + [$key, $value] = explode('=', $annotationValues); |
|
67 | + $this->annotations[$annontation][$key] = $value; |
|
68 | + } |
|
69 | + continue; |
|
70 | + } |
|
71 | + |
|
72 | + $this->annotations[$annontation] = [$annotationValue]; |
|
73 | + } |
|
74 | + |
|
75 | + // extract type parameter information |
|
76 | + preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches); |
|
77 | + $this->types = array_combine($matches['var'], $matches['type']); |
|
78 | + } |
|
79 | + |
|
80 | + foreach ($reflection->getParameters() as $param) { |
|
81 | + // extract type information from PHP 7 scalar types and prefer them over phpdoc annotations |
|
82 | + $type = $param->getType(); |
|
83 | + if ($type instanceof \ReflectionNamedType) { |
|
84 | + $this->types[$param->getName()] = $type->getName(); |
|
85 | + } |
|
86 | + |
|
87 | + $default = null; |
|
88 | + if ($param->isOptional()) { |
|
89 | + $default = $param->getDefaultValue(); |
|
90 | + } |
|
91 | + $this->parameters[$param->name] = $default; |
|
92 | + } |
|
93 | + } |
|
94 | + |
|
95 | + /** |
|
96 | + * Inspects the PHPDoc parameters for types |
|
97 | + * @param string $parameter the parameter whose type comments should be |
|
98 | + * parsed |
|
99 | + * @return string|null type in the type parameters (@param int $something) |
|
100 | + * would return int or null if not existing |
|
101 | + */ |
|
102 | + public function getType(string $parameter) { |
|
103 | + if (array_key_exists($parameter, $this->types)) { |
|
104 | + return $this->types[$parameter]; |
|
105 | + } |
|
106 | + |
|
107 | + return null; |
|
108 | + } |
|
109 | + |
|
110 | + /** |
|
111 | + * @return array the arguments of the method with key => default value |
|
112 | + */ |
|
113 | + public function getParameters(): array { |
|
114 | + return $this->parameters; |
|
115 | + } |
|
116 | + |
|
117 | + /** |
|
118 | + * Check if a method contains an annotation |
|
119 | + * @param string $name the name of the annotation |
|
120 | + * @return bool true if the annotation is found |
|
121 | + */ |
|
122 | + public function hasAnnotation(string $name): bool { |
|
123 | + $name = strtolower($name); |
|
124 | + return array_key_exists($name, $this->annotations); |
|
125 | + } |
|
126 | + |
|
127 | + /** |
|
128 | + * Get optional annotation parameter by key |
|
129 | + * |
|
130 | + * @param string $name the name of the annotation |
|
131 | + * @param string $key the string of the annotation |
|
132 | + * @return string |
|
133 | + */ |
|
134 | + public function getAnnotationParameter(string $name, string $key): string { |
|
135 | + $name = strtolower($name); |
|
136 | + if (isset($this->annotations[$name][$key])) { |
|
137 | + return $this->annotations[$name][$key]; |
|
138 | + } |
|
139 | + |
|
140 | + return ''; |
|
141 | + } |
|
142 | 142 | } |
@@ -64,875 +64,875 @@ |
||
64 | 64 | * @property mixed[] server |
65 | 65 | */ |
66 | 66 | class Request implements \ArrayAccess, \Countable, IRequest { |
67 | - public const USER_AGENT_IE = '/(MSIE)|(Trident)/'; |
|
68 | - // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx |
|
69 | - public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/'; |
|
70 | - // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference |
|
71 | - public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/'; |
|
72 | - // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent |
|
73 | - public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/'; |
|
74 | - // Safari User Agent from http://www.useragentstring.com/pages/Safari/ |
|
75 | - public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/'; |
|
76 | - // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent |
|
77 | - public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#'; |
|
78 | - public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#'; |
|
79 | - public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/'; |
|
80 | - |
|
81 | - /** |
|
82 | - * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead |
|
83 | - */ |
|
84 | - public const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/'; |
|
85 | - /** |
|
86 | - * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_ANDROID instead |
|
87 | - */ |
|
88 | - public const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/'; |
|
89 | - /** |
|
90 | - * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_DESKTOP instead |
|
91 | - */ |
|
92 | - public const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/'; |
|
93 | - |
|
94 | - protected $inputStream; |
|
95 | - protected $content; |
|
96 | - protected $items = []; |
|
97 | - protected $allowedKeys = [ |
|
98 | - 'get', |
|
99 | - 'post', |
|
100 | - 'files', |
|
101 | - 'server', |
|
102 | - 'env', |
|
103 | - 'cookies', |
|
104 | - 'urlParams', |
|
105 | - 'parameters', |
|
106 | - 'method', |
|
107 | - 'requesttoken', |
|
108 | - ]; |
|
109 | - /** @var ISecureRandom */ |
|
110 | - protected $secureRandom; |
|
111 | - /** @var IConfig */ |
|
112 | - protected $config; |
|
113 | - /** @var string */ |
|
114 | - protected $requestId = ''; |
|
115 | - /** @var ICrypto */ |
|
116 | - protected $crypto; |
|
117 | - /** @var CsrfTokenManager|null */ |
|
118 | - protected $csrfTokenManager; |
|
119 | - |
|
120 | - /** @var bool */ |
|
121 | - protected $contentDecoded = false; |
|
122 | - |
|
123 | - /** |
|
124 | - * @param array $vars An associative array with the following optional values: |
|
125 | - * - array 'urlParams' the parameters which were matched from the URL |
|
126 | - * - array 'get' the $_GET array |
|
127 | - * - array|string 'post' the $_POST array or JSON string |
|
128 | - * - array 'files' the $_FILES array |
|
129 | - * - array 'server' the $_SERVER array |
|
130 | - * - array 'env' the $_ENV array |
|
131 | - * - array 'cookies' the $_COOKIE array |
|
132 | - * - string 'method' the request method (GET, POST etc) |
|
133 | - * - string|false 'requesttoken' the requesttoken or false when not available |
|
134 | - * @param ISecureRandom $secureRandom |
|
135 | - * @param IConfig $config |
|
136 | - * @param CsrfTokenManager|null $csrfTokenManager |
|
137 | - * @param string $stream |
|
138 | - * @see https://www.php.net/manual/en/reserved.variables.php |
|
139 | - */ |
|
140 | - public function __construct(array $vars, |
|
141 | - ISecureRandom $secureRandom, |
|
142 | - IConfig $config, |
|
143 | - CsrfTokenManager $csrfTokenManager = null, |
|
144 | - string $stream = 'php://input') { |
|
145 | - $this->inputStream = $stream; |
|
146 | - $this->items['params'] = []; |
|
147 | - $this->secureRandom = $secureRandom; |
|
148 | - $this->config = $config; |
|
149 | - $this->csrfTokenManager = $csrfTokenManager; |
|
150 | - |
|
151 | - if (!array_key_exists('method', $vars)) { |
|
152 | - $vars['method'] = 'GET'; |
|
153 | - } |
|
154 | - |
|
155 | - foreach ($this->allowedKeys as $name) { |
|
156 | - $this->items[$name] = isset($vars[$name]) |
|
157 | - ? $vars[$name] |
|
158 | - : []; |
|
159 | - } |
|
160 | - |
|
161 | - $this->items['parameters'] = array_merge( |
|
162 | - $this->items['get'], |
|
163 | - $this->items['post'], |
|
164 | - $this->items['urlParams'], |
|
165 | - $this->items['params'] |
|
166 | - ); |
|
167 | - } |
|
168 | - /** |
|
169 | - * @param array $parameters |
|
170 | - */ |
|
171 | - public function setUrlParameters(array $parameters) { |
|
172 | - $this->items['urlParams'] = $parameters; |
|
173 | - $this->items['parameters'] = array_merge( |
|
174 | - $this->items['parameters'], |
|
175 | - $this->items['urlParams'] |
|
176 | - ); |
|
177 | - } |
|
178 | - |
|
179 | - /** |
|
180 | - * Countable method |
|
181 | - * @return int |
|
182 | - */ |
|
183 | - public function count(): int { |
|
184 | - return \count($this->items['parameters']); |
|
185 | - } |
|
186 | - |
|
187 | - /** |
|
188 | - * ArrayAccess methods |
|
189 | - * |
|
190 | - * Gives access to the combined GET, POST and urlParams arrays |
|
191 | - * |
|
192 | - * Examples: |
|
193 | - * |
|
194 | - * $var = $request['myvar']; |
|
195 | - * |
|
196 | - * or |
|
197 | - * |
|
198 | - * if(!isset($request['myvar']) { |
|
199 | - * // Do something |
|
200 | - * } |
|
201 | - * |
|
202 | - * $request['myvar'] = 'something'; // This throws an exception. |
|
203 | - * |
|
204 | - * @param string $offset The key to lookup |
|
205 | - * @return boolean |
|
206 | - */ |
|
207 | - public function offsetExists($offset): bool { |
|
208 | - return isset($this->items['parameters'][$offset]); |
|
209 | - } |
|
210 | - |
|
211 | - /** |
|
212 | - * @see offsetExists |
|
213 | - * @param string $offset |
|
214 | - * @return mixed |
|
215 | - */ |
|
216 | - public function offsetGet($offset) { |
|
217 | - return isset($this->items['parameters'][$offset]) |
|
218 | - ? $this->items['parameters'][$offset] |
|
219 | - : null; |
|
220 | - } |
|
221 | - |
|
222 | - /** |
|
223 | - * @see offsetExists |
|
224 | - * @param string $offset |
|
225 | - * @param mixed $value |
|
226 | - */ |
|
227 | - public function offsetSet($offset, $value) { |
|
228 | - throw new \RuntimeException('You cannot change the contents of the request object'); |
|
229 | - } |
|
230 | - |
|
231 | - /** |
|
232 | - * @see offsetExists |
|
233 | - * @param string $offset |
|
234 | - */ |
|
235 | - public function offsetUnset($offset) { |
|
236 | - throw new \RuntimeException('You cannot change the contents of the request object'); |
|
237 | - } |
|
238 | - |
|
239 | - /** |
|
240 | - * Magic property accessors |
|
241 | - * @param string $name |
|
242 | - * @param mixed $value |
|
243 | - */ |
|
244 | - public function __set($name, $value) { |
|
245 | - throw new \RuntimeException('You cannot change the contents of the request object'); |
|
246 | - } |
|
247 | - |
|
248 | - /** |
|
249 | - * Access request variables by method and name. |
|
250 | - * Examples: |
|
251 | - * |
|
252 | - * $request->post['myvar']; // Only look for POST variables |
|
253 | - * $request->myvar; or $request->{'myvar'}; or $request->{$myvar} |
|
254 | - * Looks in the combined GET, POST and urlParams array. |
|
255 | - * |
|
256 | - * If you access e.g. ->post but the current HTTP request method |
|
257 | - * is GET a \LogicException will be thrown. |
|
258 | - * |
|
259 | - * @param string $name The key to look for. |
|
260 | - * @throws \LogicException |
|
261 | - * @return mixed|null |
|
262 | - */ |
|
263 | - public function __get($name) { |
|
264 | - switch ($name) { |
|
265 | - case 'put': |
|
266 | - case 'patch': |
|
267 | - case 'get': |
|
268 | - case 'post': |
|
269 | - if ($this->method !== strtoupper($name)) { |
|
270 | - throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method)); |
|
271 | - } |
|
272 | - return $this->getContent(); |
|
273 | - case 'files': |
|
274 | - case 'server': |
|
275 | - case 'env': |
|
276 | - case 'cookies': |
|
277 | - case 'urlParams': |
|
278 | - case 'method': |
|
279 | - return isset($this->items[$name]) |
|
280 | - ? $this->items[$name] |
|
281 | - : null; |
|
282 | - case 'parameters': |
|
283 | - case 'params': |
|
284 | - return $this->getContent(); |
|
285 | - default: |
|
286 | - return isset($this[$name]) |
|
287 | - ? $this[$name] |
|
288 | - : null; |
|
289 | - } |
|
290 | - } |
|
291 | - |
|
292 | - /** |
|
293 | - * @param string $name |
|
294 | - * @return bool |
|
295 | - */ |
|
296 | - public function __isset($name) { |
|
297 | - if (\in_array($name, $this->allowedKeys, true)) { |
|
298 | - return true; |
|
299 | - } |
|
300 | - return isset($this->items['parameters'][$name]); |
|
301 | - } |
|
302 | - |
|
303 | - /** |
|
304 | - * @param string $id |
|
305 | - */ |
|
306 | - public function __unset($id) { |
|
307 | - throw new \RuntimeException('You cannot change the contents of the request object'); |
|
308 | - } |
|
309 | - |
|
310 | - /** |
|
311 | - * Returns the value for a specific http header. |
|
312 | - * |
|
313 | - * This method returns an empty string if the header did not exist. |
|
314 | - * |
|
315 | - * @param string $name |
|
316 | - * @return string |
|
317 | - */ |
|
318 | - public function getHeader(string $name): string { |
|
319 | - $name = strtoupper(str_replace('-', '_',$name)); |
|
320 | - if (isset($this->server['HTTP_' . $name])) { |
|
321 | - return $this->server['HTTP_' . $name]; |
|
322 | - } |
|
323 | - |
|
324 | - // There's a few headers that seem to end up in the top-level |
|
325 | - // server array. |
|
326 | - switch ($name) { |
|
327 | - case 'CONTENT_TYPE': |
|
328 | - case 'CONTENT_LENGTH': |
|
329 | - case 'REMOTE_ADDR': |
|
330 | - if (isset($this->server[$name])) { |
|
331 | - return $this->server[$name]; |
|
332 | - } |
|
333 | - break; |
|
334 | - } |
|
335 | - |
|
336 | - return ''; |
|
337 | - } |
|
338 | - |
|
339 | - /** |
|
340 | - * Lets you access post and get parameters by the index |
|
341 | - * In case of json requests the encoded json body is accessed |
|
342 | - * |
|
343 | - * @param string $key the key which you want to access in the URL Parameter |
|
344 | - * placeholder, $_POST or $_GET array. |
|
345 | - * The priority how they're returned is the following: |
|
346 | - * 1. URL parameters |
|
347 | - * 2. POST parameters |
|
348 | - * 3. GET parameters |
|
349 | - * @param mixed $default If the key is not found, this value will be returned |
|
350 | - * @return mixed the content of the array |
|
351 | - */ |
|
352 | - public function getParam(string $key, $default = null) { |
|
353 | - return isset($this->parameters[$key]) |
|
354 | - ? $this->parameters[$key] |
|
355 | - : $default; |
|
356 | - } |
|
357 | - |
|
358 | - /** |
|
359 | - * Returns all params that were received, be it from the request |
|
360 | - * (as GET or POST) or throuh the URL by the route |
|
361 | - * @return array the array with all parameters |
|
362 | - */ |
|
363 | - public function getParams(): array { |
|
364 | - return is_array($this->parameters) ? $this->parameters : []; |
|
365 | - } |
|
366 | - |
|
367 | - /** |
|
368 | - * Returns the method of the request |
|
369 | - * @return string the method of the request (POST, GET, etc) |
|
370 | - */ |
|
371 | - public function getMethod(): string { |
|
372 | - return $this->method; |
|
373 | - } |
|
374 | - |
|
375 | - /** |
|
376 | - * Shortcut for accessing an uploaded file through the $_FILES array |
|
377 | - * @param string $key the key that will be taken from the $_FILES array |
|
378 | - * @return array the file in the $_FILES element |
|
379 | - */ |
|
380 | - public function getUploadedFile(string $key) { |
|
381 | - return isset($this->files[$key]) ? $this->files[$key] : null; |
|
382 | - } |
|
383 | - |
|
384 | - /** |
|
385 | - * Shortcut for getting env variables |
|
386 | - * @param string $key the key that will be taken from the $_ENV array |
|
387 | - * @return array the value in the $_ENV element |
|
388 | - */ |
|
389 | - public function getEnv(string $key) { |
|
390 | - return isset($this->env[$key]) ? $this->env[$key] : null; |
|
391 | - } |
|
392 | - |
|
393 | - /** |
|
394 | - * Shortcut for getting cookie variables |
|
395 | - * @param string $key the key that will be taken from the $_COOKIE array |
|
396 | - * @return string the value in the $_COOKIE element |
|
397 | - */ |
|
398 | - public function getCookie(string $key) { |
|
399 | - return isset($this->cookies[$key]) ? $this->cookies[$key] : null; |
|
400 | - } |
|
401 | - |
|
402 | - /** |
|
403 | - * Returns the request body content. |
|
404 | - * |
|
405 | - * If the HTTP request method is PUT and the body |
|
406 | - * not application/x-www-form-urlencoded or application/json a stream |
|
407 | - * resource is returned, otherwise an array. |
|
408 | - * |
|
409 | - * @return array|string|resource The request body content or a resource to read the body stream. |
|
410 | - * |
|
411 | - * @throws \LogicException |
|
412 | - */ |
|
413 | - protected function getContent() { |
|
414 | - // If the content can't be parsed into an array then return a stream resource. |
|
415 | - if ($this->method === 'PUT' |
|
416 | - && $this->getHeader('Content-Length') !== '0' |
|
417 | - && $this->getHeader('Content-Length') !== '' |
|
418 | - && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false |
|
419 | - && strpos($this->getHeader('Content-Type'), 'application/json') === false |
|
420 | - ) { |
|
421 | - if ($this->content === false) { |
|
422 | - throw new \LogicException( |
|
423 | - '"put" can only be accessed once if not ' |
|
424 | - . 'application/x-www-form-urlencoded or application/json.' |
|
425 | - ); |
|
426 | - } |
|
427 | - $this->content = false; |
|
428 | - return fopen($this->inputStream, 'rb'); |
|
429 | - } else { |
|
430 | - $this->decodeContent(); |
|
431 | - return $this->items['parameters']; |
|
432 | - } |
|
433 | - } |
|
434 | - |
|
435 | - /** |
|
436 | - * Attempt to decode the content and populate parameters |
|
437 | - */ |
|
438 | - protected function decodeContent() { |
|
439 | - if ($this->contentDecoded) { |
|
440 | - return; |
|
441 | - } |
|
442 | - $params = []; |
|
443 | - |
|
444 | - // 'application/json' must be decoded manually. |
|
445 | - if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) { |
|
446 | - $params = json_decode(file_get_contents($this->inputStream), true); |
|
447 | - if ($params !== null && \count($params) > 0) { |
|
448 | - $this->items['params'] = $params; |
|
449 | - if ($this->method === 'POST') { |
|
450 | - $this->items['post'] = $params; |
|
451 | - } |
|
452 | - } |
|
453 | - |
|
454 | - // Handle application/x-www-form-urlencoded for methods other than GET |
|
455 | - // or post correctly |
|
456 | - } elseif ($this->method !== 'GET' |
|
457 | - && $this->method !== 'POST' |
|
458 | - && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) { |
|
459 | - parse_str(file_get_contents($this->inputStream), $params); |
|
460 | - if (\is_array($params)) { |
|
461 | - $this->items['params'] = $params; |
|
462 | - } |
|
463 | - } |
|
464 | - |
|
465 | - if (\is_array($params)) { |
|
466 | - $this->items['parameters'] = array_merge($this->items['parameters'], $params); |
|
467 | - } |
|
468 | - $this->contentDecoded = true; |
|
469 | - } |
|
470 | - |
|
471 | - |
|
472 | - /** |
|
473 | - * Checks if the CSRF check was correct |
|
474 | - * @return bool true if CSRF check passed |
|
475 | - */ |
|
476 | - public function passesCSRFCheck(): bool { |
|
477 | - if ($this->csrfTokenManager === null) { |
|
478 | - return false; |
|
479 | - } |
|
480 | - |
|
481 | - if (!$this->passesStrictCookieCheck()) { |
|
482 | - return false; |
|
483 | - } |
|
484 | - |
|
485 | - if (isset($this->items['get']['requesttoken'])) { |
|
486 | - $token = $this->items['get']['requesttoken']; |
|
487 | - } elseif (isset($this->items['post']['requesttoken'])) { |
|
488 | - $token = $this->items['post']['requesttoken']; |
|
489 | - } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) { |
|
490 | - $token = $this->items['server']['HTTP_REQUESTTOKEN']; |
|
491 | - } else { |
|
492 | - //no token found. |
|
493 | - return false; |
|
494 | - } |
|
495 | - $token = new CsrfToken($token); |
|
496 | - |
|
497 | - return $this->csrfTokenManager->isTokenValid($token); |
|
498 | - } |
|
499 | - |
|
500 | - /** |
|
501 | - * Whether the cookie checks are required |
|
502 | - * |
|
503 | - * @return bool |
|
504 | - */ |
|
505 | - private function cookieCheckRequired(): bool { |
|
506 | - if ($this->getHeader('OCS-APIREQUEST')) { |
|
507 | - return false; |
|
508 | - } |
|
509 | - if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) { |
|
510 | - return false; |
|
511 | - } |
|
512 | - |
|
513 | - return true; |
|
514 | - } |
|
515 | - |
|
516 | - /** |
|
517 | - * Wrapper around session_get_cookie_params |
|
518 | - * |
|
519 | - * @return array |
|
520 | - */ |
|
521 | - public function getCookieParams(): array { |
|
522 | - return session_get_cookie_params(); |
|
523 | - } |
|
524 | - |
|
525 | - /** |
|
526 | - * Appends the __Host- prefix to the cookie if applicable |
|
527 | - * |
|
528 | - * @param string $name |
|
529 | - * @return string |
|
530 | - */ |
|
531 | - protected function getProtectedCookieName(string $name): string { |
|
532 | - $cookieParams = $this->getCookieParams(); |
|
533 | - $prefix = ''; |
|
534 | - if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') { |
|
535 | - $prefix = '__Host-'; |
|
536 | - } |
|
537 | - |
|
538 | - return $prefix.$name; |
|
539 | - } |
|
540 | - |
|
541 | - /** |
|
542 | - * Checks if the strict cookie has been sent with the request if the request |
|
543 | - * is including any cookies. |
|
544 | - * |
|
545 | - * @return bool |
|
546 | - * @since 9.1.0 |
|
547 | - */ |
|
548 | - public function passesStrictCookieCheck(): bool { |
|
549 | - if (!$this->cookieCheckRequired()) { |
|
550 | - return true; |
|
551 | - } |
|
552 | - |
|
553 | - $cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict'); |
|
554 | - if ($this->getCookie($cookieName) === 'true' |
|
555 | - && $this->passesLaxCookieCheck()) { |
|
556 | - return true; |
|
557 | - } |
|
558 | - return false; |
|
559 | - } |
|
560 | - |
|
561 | - /** |
|
562 | - * Checks if the lax cookie has been sent with the request if the request |
|
563 | - * is including any cookies. |
|
564 | - * |
|
565 | - * @return bool |
|
566 | - * @since 9.1.0 |
|
567 | - */ |
|
568 | - public function passesLaxCookieCheck(): bool { |
|
569 | - if (!$this->cookieCheckRequired()) { |
|
570 | - return true; |
|
571 | - } |
|
572 | - |
|
573 | - $cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax'); |
|
574 | - if ($this->getCookie($cookieName) === 'true') { |
|
575 | - return true; |
|
576 | - } |
|
577 | - return false; |
|
578 | - } |
|
579 | - |
|
580 | - |
|
581 | - /** |
|
582 | - * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging |
|
583 | - * If `mod_unique_id` is installed this value will be taken. |
|
584 | - * @return string |
|
585 | - */ |
|
586 | - public function getId(): string { |
|
587 | - if (isset($this->server['UNIQUE_ID'])) { |
|
588 | - return $this->server['UNIQUE_ID']; |
|
589 | - } |
|
590 | - |
|
591 | - if (empty($this->requestId)) { |
|
592 | - $validChars = ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS; |
|
593 | - $this->requestId = $this->secureRandom->generate(20, $validChars); |
|
594 | - } |
|
595 | - |
|
596 | - return $this->requestId; |
|
597 | - } |
|
598 | - |
|
599 | - /** |
|
600 | - * Checks if given $remoteAddress matches given $trustedProxy. |
|
601 | - * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if |
|
602 | - * $remoteAddress is an IPv4 address within that IP range. |
|
603 | - * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result |
|
604 | - * will be returned. |
|
605 | - * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise |
|
606 | - */ |
|
607 | - protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { |
|
608 | - $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; |
|
609 | - |
|
610 | - if (preg_match($cidrre, $trustedProxy, $match)) { |
|
611 | - $net = $match[1]; |
|
612 | - $shiftbits = min(32, max(0, 32 - intval($match[2]))); |
|
613 | - $netnum = ip2long($net) >> $shiftbits; |
|
614 | - $ipnum = ip2long($remoteAddress) >> $shiftbits; |
|
615 | - |
|
616 | - return $ipnum === $netnum; |
|
617 | - } |
|
618 | - |
|
619 | - return $trustedProxy === $remoteAddress; |
|
620 | - } |
|
621 | - |
|
622 | - /** |
|
623 | - * Checks if given $remoteAddress matches any entry in the given array $trustedProxies. |
|
624 | - * For details regarding what "match" means, refer to `matchesTrustedProxy`. |
|
625 | - * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise |
|
626 | - */ |
|
627 | - protected function isTrustedProxy($trustedProxies, $remoteAddress) { |
|
628 | - foreach ($trustedProxies as $tp) { |
|
629 | - if ($this->matchesTrustedProxy($tp, $remoteAddress)) { |
|
630 | - return true; |
|
631 | - } |
|
632 | - } |
|
633 | - |
|
634 | - return false; |
|
635 | - } |
|
636 | - |
|
637 | - /** |
|
638 | - * Returns the remote address, if the connection came from a trusted proxy |
|
639 | - * and `forwarded_for_headers` has been configured then the IP address |
|
640 | - * specified in this header will be returned instead. |
|
641 | - * Do always use this instead of $_SERVER['REMOTE_ADDR'] |
|
642 | - * @return string IP address |
|
643 | - */ |
|
644 | - public function getRemoteAddress(): string { |
|
645 | - $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; |
|
646 | - $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); |
|
647 | - |
|
648 | - if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) { |
|
649 | - $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [ |
|
650 | - 'HTTP_X_FORWARDED_FOR' |
|
651 | - // only have one default, so we cannot ship an insecure product out of the box |
|
652 | - ]); |
|
653 | - |
|
654 | - foreach ($forwardedForHeaders as $header) { |
|
655 | - if (isset($this->server[$header])) { |
|
656 | - foreach (explode(',', $this->server[$header]) as $IP) { |
|
657 | - $IP = trim($IP); |
|
658 | - |
|
659 | - // remove brackets from IPv6 addresses |
|
660 | - if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') { |
|
661 | - $IP = substr($IP, 1, -1); |
|
662 | - } |
|
663 | - |
|
664 | - if (filter_var($IP, FILTER_VALIDATE_IP) !== false) { |
|
665 | - return $IP; |
|
666 | - } |
|
667 | - } |
|
668 | - } |
|
669 | - } |
|
670 | - } |
|
671 | - |
|
672 | - return $remoteAddress; |
|
673 | - } |
|
674 | - |
|
675 | - /** |
|
676 | - * Check overwrite condition |
|
677 | - * @param string $type |
|
678 | - * @return bool |
|
679 | - */ |
|
680 | - private function isOverwriteCondition(string $type = ''): bool { |
|
681 | - $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/'; |
|
682 | - $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; |
|
683 | - return $regex === '//' || preg_match($regex, $remoteAddr) === 1 |
|
684 | - || $type !== 'protocol'; |
|
685 | - } |
|
686 | - |
|
687 | - /** |
|
688 | - * Returns the server protocol. It respects one or more reverse proxies servers |
|
689 | - * and load balancers |
|
690 | - * @return string Server protocol (http or https) |
|
691 | - */ |
|
692 | - public function getServerProtocol(): string { |
|
693 | - if ($this->config->getSystemValue('overwriteprotocol') !== '' |
|
694 | - && $this->isOverwriteCondition('protocol')) { |
|
695 | - return $this->config->getSystemValue('overwriteprotocol'); |
|
696 | - } |
|
697 | - |
|
698 | - if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) { |
|
699 | - if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) { |
|
700 | - $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']); |
|
701 | - $proto = strtolower(trim($parts[0])); |
|
702 | - } else { |
|
703 | - $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']); |
|
704 | - } |
|
705 | - |
|
706 | - // Verify that the protocol is always HTTP or HTTPS |
|
707 | - // default to http if an invalid value is provided |
|
708 | - return $proto === 'https' ? 'https' : 'http'; |
|
709 | - } |
|
710 | - |
|
711 | - if (isset($this->server['HTTPS']) |
|
712 | - && $this->server['HTTPS'] !== null |
|
713 | - && $this->server['HTTPS'] !== 'off' |
|
714 | - && $this->server['HTTPS'] !== '') { |
|
715 | - return 'https'; |
|
716 | - } |
|
717 | - |
|
718 | - return 'http'; |
|
719 | - } |
|
720 | - |
|
721 | - /** |
|
722 | - * Returns the used HTTP protocol. |
|
723 | - * |
|
724 | - * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. |
|
725 | - */ |
|
726 | - public function getHttpProtocol(): string { |
|
727 | - $claimedProtocol = $this->server['SERVER_PROTOCOL']; |
|
728 | - |
|
729 | - if (\is_string($claimedProtocol)) { |
|
730 | - $claimedProtocol = strtoupper($claimedProtocol); |
|
731 | - } |
|
732 | - |
|
733 | - $validProtocols = [ |
|
734 | - 'HTTP/1.0', |
|
735 | - 'HTTP/1.1', |
|
736 | - 'HTTP/2', |
|
737 | - ]; |
|
738 | - |
|
739 | - if (\in_array($claimedProtocol, $validProtocols, true)) { |
|
740 | - return $claimedProtocol; |
|
741 | - } |
|
742 | - |
|
743 | - return 'HTTP/1.1'; |
|
744 | - } |
|
745 | - |
|
746 | - /** |
|
747 | - * Returns the request uri, even if the website uses one or more |
|
748 | - * reverse proxies |
|
749 | - * @return string |
|
750 | - */ |
|
751 | - public function getRequestUri(): string { |
|
752 | - $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; |
|
753 | - if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) { |
|
754 | - $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME'])); |
|
755 | - } |
|
756 | - return $uri; |
|
757 | - } |
|
758 | - |
|
759 | - /** |
|
760 | - * Get raw PathInfo from request (not urldecoded) |
|
761 | - * @throws \Exception |
|
762 | - * @return string Path info |
|
763 | - */ |
|
764 | - public function getRawPathInfo(): string { |
|
765 | - $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; |
|
766 | - // remove too many slashes - can be caused by reverse proxy configuration |
|
767 | - $requestUri = preg_replace('%/{2,}%', '/', $requestUri); |
|
768 | - |
|
769 | - // Remove the query string from REQUEST_URI |
|
770 | - if ($pos = strpos($requestUri, '?')) { |
|
771 | - $requestUri = substr($requestUri, 0, $pos); |
|
772 | - } |
|
773 | - |
|
774 | - $scriptName = $this->server['SCRIPT_NAME']; |
|
775 | - $pathInfo = $requestUri; |
|
776 | - |
|
777 | - // strip off the script name's dir and file name |
|
778 | - // FIXME: Sabre does not really belong here |
|
779 | - [$path, $name] = \Sabre\Uri\split($scriptName); |
|
780 | - if (!empty($path)) { |
|
781 | - if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) { |
|
782 | - $pathInfo = substr($pathInfo, \strlen($path)); |
|
783 | - } else { |
|
784 | - throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')"); |
|
785 | - } |
|
786 | - } |
|
787 | - if ($name === null) { |
|
788 | - $name = ''; |
|
789 | - } |
|
790 | - |
|
791 | - if (strpos($pathInfo, '/'.$name) === 0) { |
|
792 | - $pathInfo = substr($pathInfo, \strlen($name) + 1); |
|
793 | - } |
|
794 | - if ($name !== '' && strpos($pathInfo, $name) === 0) { |
|
795 | - $pathInfo = substr($pathInfo, \strlen($name)); |
|
796 | - } |
|
797 | - if ($pathInfo === false || $pathInfo === '/') { |
|
798 | - return ''; |
|
799 | - } else { |
|
800 | - return $pathInfo; |
|
801 | - } |
|
802 | - } |
|
803 | - |
|
804 | - /** |
|
805 | - * Get PathInfo from request |
|
806 | - * @throws \Exception |
|
807 | - * @return string|false Path info or false when not found |
|
808 | - */ |
|
809 | - public function getPathInfo() { |
|
810 | - $pathInfo = $this->getRawPathInfo(); |
|
811 | - // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment |
|
812 | - $pathInfo = rawurldecode($pathInfo); |
|
813 | - $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']); |
|
814 | - |
|
815 | - switch ($encoding) { |
|
816 | - case 'ISO-8859-1': |
|
817 | - $pathInfo = utf8_encode($pathInfo); |
|
818 | - } |
|
819 | - // end copy |
|
820 | - |
|
821 | - return $pathInfo; |
|
822 | - } |
|
823 | - |
|
824 | - /** |
|
825 | - * Returns the script name, even if the website uses one or more |
|
826 | - * reverse proxies |
|
827 | - * @return string the script name |
|
828 | - */ |
|
829 | - public function getScriptName(): string { |
|
830 | - $name = $this->server['SCRIPT_NAME']; |
|
831 | - $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); |
|
832 | - if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) { |
|
833 | - // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous |
|
834 | - $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/'))); |
|
835 | - $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot))); |
|
836 | - $name = '/' . ltrim($overwriteWebRoot . $suburi, '/'); |
|
837 | - } |
|
838 | - return $name; |
|
839 | - } |
|
840 | - |
|
841 | - /** |
|
842 | - * Checks whether the user agent matches a given regex |
|
843 | - * @param array $agent array of agent names |
|
844 | - * @return bool true if at least one of the given agent matches, false otherwise |
|
845 | - */ |
|
846 | - public function isUserAgent(array $agent): bool { |
|
847 | - if (!isset($this->server['HTTP_USER_AGENT'])) { |
|
848 | - return false; |
|
849 | - } |
|
850 | - foreach ($agent as $regex) { |
|
851 | - if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) { |
|
852 | - return true; |
|
853 | - } |
|
854 | - } |
|
855 | - return false; |
|
856 | - } |
|
857 | - |
|
858 | - /** |
|
859 | - * Returns the unverified server host from the headers without checking |
|
860 | - * whether it is a trusted domain |
|
861 | - * @return string Server host |
|
862 | - */ |
|
863 | - public function getInsecureServerHost(): string { |
|
864 | - if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) { |
|
865 | - return $this->getOverwriteHost(); |
|
866 | - } |
|
867 | - |
|
868 | - $host = 'localhost'; |
|
869 | - if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) { |
|
870 | - if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) { |
|
871 | - $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']); |
|
872 | - $host = trim(current($parts)); |
|
873 | - } else { |
|
874 | - $host = $this->server['HTTP_X_FORWARDED_HOST']; |
|
875 | - } |
|
876 | - } else { |
|
877 | - if (isset($this->server['HTTP_HOST'])) { |
|
878 | - $host = $this->server['HTTP_HOST']; |
|
879 | - } elseif (isset($this->server['SERVER_NAME'])) { |
|
880 | - $host = $this->server['SERVER_NAME']; |
|
881 | - } |
|
882 | - } |
|
883 | - |
|
884 | - return $host; |
|
885 | - } |
|
886 | - |
|
887 | - |
|
888 | - /** |
|
889 | - * Returns the server host from the headers, or the first configured |
|
890 | - * trusted domain if the host isn't in the trusted list |
|
891 | - * @return string Server host |
|
892 | - */ |
|
893 | - public function getServerHost(): string { |
|
894 | - // overwritehost is always trusted |
|
895 | - $host = $this->getOverwriteHost(); |
|
896 | - if ($host !== null) { |
|
897 | - return $host; |
|
898 | - } |
|
899 | - |
|
900 | - // get the host from the headers |
|
901 | - $host = $this->getInsecureServerHost(); |
|
902 | - |
|
903 | - // Verify that the host is a trusted domain if the trusted domains |
|
904 | - // are defined |
|
905 | - // If no trusted domain is provided the first trusted domain is returned |
|
906 | - $trustedDomainHelper = new TrustedDomainHelper($this->config); |
|
907 | - if ($trustedDomainHelper->isTrustedDomain($host)) { |
|
908 | - return $host; |
|
909 | - } |
|
910 | - |
|
911 | - $trustedList = (array)$this->config->getSystemValue('trusted_domains', []); |
|
912 | - if (count($trustedList) > 0) { |
|
913 | - return reset($trustedList); |
|
914 | - } |
|
915 | - |
|
916 | - return ''; |
|
917 | - } |
|
918 | - |
|
919 | - /** |
|
920 | - * Returns the overwritehost setting from the config if set and |
|
921 | - * if the overwrite condition is met |
|
922 | - * @return string|null overwritehost value or null if not defined or the defined condition |
|
923 | - * isn't met |
|
924 | - */ |
|
925 | - private function getOverwriteHost() { |
|
926 | - if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) { |
|
927 | - return $this->config->getSystemValue('overwritehost'); |
|
928 | - } |
|
929 | - return null; |
|
930 | - } |
|
931 | - |
|
932 | - private function fromTrustedProxy(): bool { |
|
933 | - $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; |
|
934 | - $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); |
|
935 | - |
|
936 | - return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress); |
|
937 | - } |
|
67 | + public const USER_AGENT_IE = '/(MSIE)|(Trident)/'; |
|
68 | + // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx |
|
69 | + public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/'; |
|
70 | + // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference |
|
71 | + public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/'; |
|
72 | + // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent |
|
73 | + public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/'; |
|
74 | + // Safari User Agent from http://www.useragentstring.com/pages/Safari/ |
|
75 | + public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/'; |
|
76 | + // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent |
|
77 | + public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#'; |
|
78 | + public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#'; |
|
79 | + public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/'; |
|
80 | + |
|
81 | + /** |
|
82 | + * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead |
|
83 | + */ |
|
84 | + public const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/'; |
|
85 | + /** |
|
86 | + * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_ANDROID instead |
|
87 | + */ |
|
88 | + public const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/'; |
|
89 | + /** |
|
90 | + * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_DESKTOP instead |
|
91 | + */ |
|
92 | + public const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/'; |
|
93 | + |
|
94 | + protected $inputStream; |
|
95 | + protected $content; |
|
96 | + protected $items = []; |
|
97 | + protected $allowedKeys = [ |
|
98 | + 'get', |
|
99 | + 'post', |
|
100 | + 'files', |
|
101 | + 'server', |
|
102 | + 'env', |
|
103 | + 'cookies', |
|
104 | + 'urlParams', |
|
105 | + 'parameters', |
|
106 | + 'method', |
|
107 | + 'requesttoken', |
|
108 | + ]; |
|
109 | + /** @var ISecureRandom */ |
|
110 | + protected $secureRandom; |
|
111 | + /** @var IConfig */ |
|
112 | + protected $config; |
|
113 | + /** @var string */ |
|
114 | + protected $requestId = ''; |
|
115 | + /** @var ICrypto */ |
|
116 | + protected $crypto; |
|
117 | + /** @var CsrfTokenManager|null */ |
|
118 | + protected $csrfTokenManager; |
|
119 | + |
|
120 | + /** @var bool */ |
|
121 | + protected $contentDecoded = false; |
|
122 | + |
|
123 | + /** |
|
124 | + * @param array $vars An associative array with the following optional values: |
|
125 | + * - array 'urlParams' the parameters which were matched from the URL |
|
126 | + * - array 'get' the $_GET array |
|
127 | + * - array|string 'post' the $_POST array or JSON string |
|
128 | + * - array 'files' the $_FILES array |
|
129 | + * - array 'server' the $_SERVER array |
|
130 | + * - array 'env' the $_ENV array |
|
131 | + * - array 'cookies' the $_COOKIE array |
|
132 | + * - string 'method' the request method (GET, POST etc) |
|
133 | + * - string|false 'requesttoken' the requesttoken or false when not available |
|
134 | + * @param ISecureRandom $secureRandom |
|
135 | + * @param IConfig $config |
|
136 | + * @param CsrfTokenManager|null $csrfTokenManager |
|
137 | + * @param string $stream |
|
138 | + * @see https://www.php.net/manual/en/reserved.variables.php |
|
139 | + */ |
|
140 | + public function __construct(array $vars, |
|
141 | + ISecureRandom $secureRandom, |
|
142 | + IConfig $config, |
|
143 | + CsrfTokenManager $csrfTokenManager = null, |
|
144 | + string $stream = 'php://input') { |
|
145 | + $this->inputStream = $stream; |
|
146 | + $this->items['params'] = []; |
|
147 | + $this->secureRandom = $secureRandom; |
|
148 | + $this->config = $config; |
|
149 | + $this->csrfTokenManager = $csrfTokenManager; |
|
150 | + |
|
151 | + if (!array_key_exists('method', $vars)) { |
|
152 | + $vars['method'] = 'GET'; |
|
153 | + } |
|
154 | + |
|
155 | + foreach ($this->allowedKeys as $name) { |
|
156 | + $this->items[$name] = isset($vars[$name]) |
|
157 | + ? $vars[$name] |
|
158 | + : []; |
|
159 | + } |
|
160 | + |
|
161 | + $this->items['parameters'] = array_merge( |
|
162 | + $this->items['get'], |
|
163 | + $this->items['post'], |
|
164 | + $this->items['urlParams'], |
|
165 | + $this->items['params'] |
|
166 | + ); |
|
167 | + } |
|
168 | + /** |
|
169 | + * @param array $parameters |
|
170 | + */ |
|
171 | + public function setUrlParameters(array $parameters) { |
|
172 | + $this->items['urlParams'] = $parameters; |
|
173 | + $this->items['parameters'] = array_merge( |
|
174 | + $this->items['parameters'], |
|
175 | + $this->items['urlParams'] |
|
176 | + ); |
|
177 | + } |
|
178 | + |
|
179 | + /** |
|
180 | + * Countable method |
|
181 | + * @return int |
|
182 | + */ |
|
183 | + public function count(): int { |
|
184 | + return \count($this->items['parameters']); |
|
185 | + } |
|
186 | + |
|
187 | + /** |
|
188 | + * ArrayAccess methods |
|
189 | + * |
|
190 | + * Gives access to the combined GET, POST and urlParams arrays |
|
191 | + * |
|
192 | + * Examples: |
|
193 | + * |
|
194 | + * $var = $request['myvar']; |
|
195 | + * |
|
196 | + * or |
|
197 | + * |
|
198 | + * if(!isset($request['myvar']) { |
|
199 | + * // Do something |
|
200 | + * } |
|
201 | + * |
|
202 | + * $request['myvar'] = 'something'; // This throws an exception. |
|
203 | + * |
|
204 | + * @param string $offset The key to lookup |
|
205 | + * @return boolean |
|
206 | + */ |
|
207 | + public function offsetExists($offset): bool { |
|
208 | + return isset($this->items['parameters'][$offset]); |
|
209 | + } |
|
210 | + |
|
211 | + /** |
|
212 | + * @see offsetExists |
|
213 | + * @param string $offset |
|
214 | + * @return mixed |
|
215 | + */ |
|
216 | + public function offsetGet($offset) { |
|
217 | + return isset($this->items['parameters'][$offset]) |
|
218 | + ? $this->items['parameters'][$offset] |
|
219 | + : null; |
|
220 | + } |
|
221 | + |
|
222 | + /** |
|
223 | + * @see offsetExists |
|
224 | + * @param string $offset |
|
225 | + * @param mixed $value |
|
226 | + */ |
|
227 | + public function offsetSet($offset, $value) { |
|
228 | + throw new \RuntimeException('You cannot change the contents of the request object'); |
|
229 | + } |
|
230 | + |
|
231 | + /** |
|
232 | + * @see offsetExists |
|
233 | + * @param string $offset |
|
234 | + */ |
|
235 | + public function offsetUnset($offset) { |
|
236 | + throw new \RuntimeException('You cannot change the contents of the request object'); |
|
237 | + } |
|
238 | + |
|
239 | + /** |
|
240 | + * Magic property accessors |
|
241 | + * @param string $name |
|
242 | + * @param mixed $value |
|
243 | + */ |
|
244 | + public function __set($name, $value) { |
|
245 | + throw new \RuntimeException('You cannot change the contents of the request object'); |
|
246 | + } |
|
247 | + |
|
248 | + /** |
|
249 | + * Access request variables by method and name. |
|
250 | + * Examples: |
|
251 | + * |
|
252 | + * $request->post['myvar']; // Only look for POST variables |
|
253 | + * $request->myvar; or $request->{'myvar'}; or $request->{$myvar} |
|
254 | + * Looks in the combined GET, POST and urlParams array. |
|
255 | + * |
|
256 | + * If you access e.g. ->post but the current HTTP request method |
|
257 | + * is GET a \LogicException will be thrown. |
|
258 | + * |
|
259 | + * @param string $name The key to look for. |
|
260 | + * @throws \LogicException |
|
261 | + * @return mixed|null |
|
262 | + */ |
|
263 | + public function __get($name) { |
|
264 | + switch ($name) { |
|
265 | + case 'put': |
|
266 | + case 'patch': |
|
267 | + case 'get': |
|
268 | + case 'post': |
|
269 | + if ($this->method !== strtoupper($name)) { |
|
270 | + throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method)); |
|
271 | + } |
|
272 | + return $this->getContent(); |
|
273 | + case 'files': |
|
274 | + case 'server': |
|
275 | + case 'env': |
|
276 | + case 'cookies': |
|
277 | + case 'urlParams': |
|
278 | + case 'method': |
|
279 | + return isset($this->items[$name]) |
|
280 | + ? $this->items[$name] |
|
281 | + : null; |
|
282 | + case 'parameters': |
|
283 | + case 'params': |
|
284 | + return $this->getContent(); |
|
285 | + default: |
|
286 | + return isset($this[$name]) |
|
287 | + ? $this[$name] |
|
288 | + : null; |
|
289 | + } |
|
290 | + } |
|
291 | + |
|
292 | + /** |
|
293 | + * @param string $name |
|
294 | + * @return bool |
|
295 | + */ |
|
296 | + public function __isset($name) { |
|
297 | + if (\in_array($name, $this->allowedKeys, true)) { |
|
298 | + return true; |
|
299 | + } |
|
300 | + return isset($this->items['parameters'][$name]); |
|
301 | + } |
|
302 | + |
|
303 | + /** |
|
304 | + * @param string $id |
|
305 | + */ |
|
306 | + public function __unset($id) { |
|
307 | + throw new \RuntimeException('You cannot change the contents of the request object'); |
|
308 | + } |
|
309 | + |
|
310 | + /** |
|
311 | + * Returns the value for a specific http header. |
|
312 | + * |
|
313 | + * This method returns an empty string if the header did not exist. |
|
314 | + * |
|
315 | + * @param string $name |
|
316 | + * @return string |
|
317 | + */ |
|
318 | + public function getHeader(string $name): string { |
|
319 | + $name = strtoupper(str_replace('-', '_',$name)); |
|
320 | + if (isset($this->server['HTTP_' . $name])) { |
|
321 | + return $this->server['HTTP_' . $name]; |
|
322 | + } |
|
323 | + |
|
324 | + // There's a few headers that seem to end up in the top-level |
|
325 | + // server array. |
|
326 | + switch ($name) { |
|
327 | + case 'CONTENT_TYPE': |
|
328 | + case 'CONTENT_LENGTH': |
|
329 | + case 'REMOTE_ADDR': |
|
330 | + if (isset($this->server[$name])) { |
|
331 | + return $this->server[$name]; |
|
332 | + } |
|
333 | + break; |
|
334 | + } |
|
335 | + |
|
336 | + return ''; |
|
337 | + } |
|
338 | + |
|
339 | + /** |
|
340 | + * Lets you access post and get parameters by the index |
|
341 | + * In case of json requests the encoded json body is accessed |
|
342 | + * |
|
343 | + * @param string $key the key which you want to access in the URL Parameter |
|
344 | + * placeholder, $_POST or $_GET array. |
|
345 | + * The priority how they're returned is the following: |
|
346 | + * 1. URL parameters |
|
347 | + * 2. POST parameters |
|
348 | + * 3. GET parameters |
|
349 | + * @param mixed $default If the key is not found, this value will be returned |
|
350 | + * @return mixed the content of the array |
|
351 | + */ |
|
352 | + public function getParam(string $key, $default = null) { |
|
353 | + return isset($this->parameters[$key]) |
|
354 | + ? $this->parameters[$key] |
|
355 | + : $default; |
|
356 | + } |
|
357 | + |
|
358 | + /** |
|
359 | + * Returns all params that were received, be it from the request |
|
360 | + * (as GET or POST) or throuh the URL by the route |
|
361 | + * @return array the array with all parameters |
|
362 | + */ |
|
363 | + public function getParams(): array { |
|
364 | + return is_array($this->parameters) ? $this->parameters : []; |
|
365 | + } |
|
366 | + |
|
367 | + /** |
|
368 | + * Returns the method of the request |
|
369 | + * @return string the method of the request (POST, GET, etc) |
|
370 | + */ |
|
371 | + public function getMethod(): string { |
|
372 | + return $this->method; |
|
373 | + } |
|
374 | + |
|
375 | + /** |
|
376 | + * Shortcut for accessing an uploaded file through the $_FILES array |
|
377 | + * @param string $key the key that will be taken from the $_FILES array |
|
378 | + * @return array the file in the $_FILES element |
|
379 | + */ |
|
380 | + public function getUploadedFile(string $key) { |
|
381 | + return isset($this->files[$key]) ? $this->files[$key] : null; |
|
382 | + } |
|
383 | + |
|
384 | + /** |
|
385 | + * Shortcut for getting env variables |
|
386 | + * @param string $key the key that will be taken from the $_ENV array |
|
387 | + * @return array the value in the $_ENV element |
|
388 | + */ |
|
389 | + public function getEnv(string $key) { |
|
390 | + return isset($this->env[$key]) ? $this->env[$key] : null; |
|
391 | + } |
|
392 | + |
|
393 | + /** |
|
394 | + * Shortcut for getting cookie variables |
|
395 | + * @param string $key the key that will be taken from the $_COOKIE array |
|
396 | + * @return string the value in the $_COOKIE element |
|
397 | + */ |
|
398 | + public function getCookie(string $key) { |
|
399 | + return isset($this->cookies[$key]) ? $this->cookies[$key] : null; |
|
400 | + } |
|
401 | + |
|
402 | + /** |
|
403 | + * Returns the request body content. |
|
404 | + * |
|
405 | + * If the HTTP request method is PUT and the body |
|
406 | + * not application/x-www-form-urlencoded or application/json a stream |
|
407 | + * resource is returned, otherwise an array. |
|
408 | + * |
|
409 | + * @return array|string|resource The request body content or a resource to read the body stream. |
|
410 | + * |
|
411 | + * @throws \LogicException |
|
412 | + */ |
|
413 | + protected function getContent() { |
|
414 | + // If the content can't be parsed into an array then return a stream resource. |
|
415 | + if ($this->method === 'PUT' |
|
416 | + && $this->getHeader('Content-Length') !== '0' |
|
417 | + && $this->getHeader('Content-Length') !== '' |
|
418 | + && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false |
|
419 | + && strpos($this->getHeader('Content-Type'), 'application/json') === false |
|
420 | + ) { |
|
421 | + if ($this->content === false) { |
|
422 | + throw new \LogicException( |
|
423 | + '"put" can only be accessed once if not ' |
|
424 | + . 'application/x-www-form-urlencoded or application/json.' |
|
425 | + ); |
|
426 | + } |
|
427 | + $this->content = false; |
|
428 | + return fopen($this->inputStream, 'rb'); |
|
429 | + } else { |
|
430 | + $this->decodeContent(); |
|
431 | + return $this->items['parameters']; |
|
432 | + } |
|
433 | + } |
|
434 | + |
|
435 | + /** |
|
436 | + * Attempt to decode the content and populate parameters |
|
437 | + */ |
|
438 | + protected function decodeContent() { |
|
439 | + if ($this->contentDecoded) { |
|
440 | + return; |
|
441 | + } |
|
442 | + $params = []; |
|
443 | + |
|
444 | + // 'application/json' must be decoded manually. |
|
445 | + if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) { |
|
446 | + $params = json_decode(file_get_contents($this->inputStream), true); |
|
447 | + if ($params !== null && \count($params) > 0) { |
|
448 | + $this->items['params'] = $params; |
|
449 | + if ($this->method === 'POST') { |
|
450 | + $this->items['post'] = $params; |
|
451 | + } |
|
452 | + } |
|
453 | + |
|
454 | + // Handle application/x-www-form-urlencoded for methods other than GET |
|
455 | + // or post correctly |
|
456 | + } elseif ($this->method !== 'GET' |
|
457 | + && $this->method !== 'POST' |
|
458 | + && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) { |
|
459 | + parse_str(file_get_contents($this->inputStream), $params); |
|
460 | + if (\is_array($params)) { |
|
461 | + $this->items['params'] = $params; |
|
462 | + } |
|
463 | + } |
|
464 | + |
|
465 | + if (\is_array($params)) { |
|
466 | + $this->items['parameters'] = array_merge($this->items['parameters'], $params); |
|
467 | + } |
|
468 | + $this->contentDecoded = true; |
|
469 | + } |
|
470 | + |
|
471 | + |
|
472 | + /** |
|
473 | + * Checks if the CSRF check was correct |
|
474 | + * @return bool true if CSRF check passed |
|
475 | + */ |
|
476 | + public function passesCSRFCheck(): bool { |
|
477 | + if ($this->csrfTokenManager === null) { |
|
478 | + return false; |
|
479 | + } |
|
480 | + |
|
481 | + if (!$this->passesStrictCookieCheck()) { |
|
482 | + return false; |
|
483 | + } |
|
484 | + |
|
485 | + if (isset($this->items['get']['requesttoken'])) { |
|
486 | + $token = $this->items['get']['requesttoken']; |
|
487 | + } elseif (isset($this->items['post']['requesttoken'])) { |
|
488 | + $token = $this->items['post']['requesttoken']; |
|
489 | + } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) { |
|
490 | + $token = $this->items['server']['HTTP_REQUESTTOKEN']; |
|
491 | + } else { |
|
492 | + //no token found. |
|
493 | + return false; |
|
494 | + } |
|
495 | + $token = new CsrfToken($token); |
|
496 | + |
|
497 | + return $this->csrfTokenManager->isTokenValid($token); |
|
498 | + } |
|
499 | + |
|
500 | + /** |
|
501 | + * Whether the cookie checks are required |
|
502 | + * |
|
503 | + * @return bool |
|
504 | + */ |
|
505 | + private function cookieCheckRequired(): bool { |
|
506 | + if ($this->getHeader('OCS-APIREQUEST')) { |
|
507 | + return false; |
|
508 | + } |
|
509 | + if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) { |
|
510 | + return false; |
|
511 | + } |
|
512 | + |
|
513 | + return true; |
|
514 | + } |
|
515 | + |
|
516 | + /** |
|
517 | + * Wrapper around session_get_cookie_params |
|
518 | + * |
|
519 | + * @return array |
|
520 | + */ |
|
521 | + public function getCookieParams(): array { |
|
522 | + return session_get_cookie_params(); |
|
523 | + } |
|
524 | + |
|
525 | + /** |
|
526 | + * Appends the __Host- prefix to the cookie if applicable |
|
527 | + * |
|
528 | + * @param string $name |
|
529 | + * @return string |
|
530 | + */ |
|
531 | + protected function getProtectedCookieName(string $name): string { |
|
532 | + $cookieParams = $this->getCookieParams(); |
|
533 | + $prefix = ''; |
|
534 | + if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') { |
|
535 | + $prefix = '__Host-'; |
|
536 | + } |
|
537 | + |
|
538 | + return $prefix.$name; |
|
539 | + } |
|
540 | + |
|
541 | + /** |
|
542 | + * Checks if the strict cookie has been sent with the request if the request |
|
543 | + * is including any cookies. |
|
544 | + * |
|
545 | + * @return bool |
|
546 | + * @since 9.1.0 |
|
547 | + */ |
|
548 | + public function passesStrictCookieCheck(): bool { |
|
549 | + if (!$this->cookieCheckRequired()) { |
|
550 | + return true; |
|
551 | + } |
|
552 | + |
|
553 | + $cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict'); |
|
554 | + if ($this->getCookie($cookieName) === 'true' |
|
555 | + && $this->passesLaxCookieCheck()) { |
|
556 | + return true; |
|
557 | + } |
|
558 | + return false; |
|
559 | + } |
|
560 | + |
|
561 | + /** |
|
562 | + * Checks if the lax cookie has been sent with the request if the request |
|
563 | + * is including any cookies. |
|
564 | + * |
|
565 | + * @return bool |
|
566 | + * @since 9.1.0 |
|
567 | + */ |
|
568 | + public function passesLaxCookieCheck(): bool { |
|
569 | + if (!$this->cookieCheckRequired()) { |
|
570 | + return true; |
|
571 | + } |
|
572 | + |
|
573 | + $cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax'); |
|
574 | + if ($this->getCookie($cookieName) === 'true') { |
|
575 | + return true; |
|
576 | + } |
|
577 | + return false; |
|
578 | + } |
|
579 | + |
|
580 | + |
|
581 | + /** |
|
582 | + * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging |
|
583 | + * If `mod_unique_id` is installed this value will be taken. |
|
584 | + * @return string |
|
585 | + */ |
|
586 | + public function getId(): string { |
|
587 | + if (isset($this->server['UNIQUE_ID'])) { |
|
588 | + return $this->server['UNIQUE_ID']; |
|
589 | + } |
|
590 | + |
|
591 | + if (empty($this->requestId)) { |
|
592 | + $validChars = ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS; |
|
593 | + $this->requestId = $this->secureRandom->generate(20, $validChars); |
|
594 | + } |
|
595 | + |
|
596 | + return $this->requestId; |
|
597 | + } |
|
598 | + |
|
599 | + /** |
|
600 | + * Checks if given $remoteAddress matches given $trustedProxy. |
|
601 | + * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if |
|
602 | + * $remoteAddress is an IPv4 address within that IP range. |
|
603 | + * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result |
|
604 | + * will be returned. |
|
605 | + * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise |
|
606 | + */ |
|
607 | + protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { |
|
608 | + $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; |
|
609 | + |
|
610 | + if (preg_match($cidrre, $trustedProxy, $match)) { |
|
611 | + $net = $match[1]; |
|
612 | + $shiftbits = min(32, max(0, 32 - intval($match[2]))); |
|
613 | + $netnum = ip2long($net) >> $shiftbits; |
|
614 | + $ipnum = ip2long($remoteAddress) >> $shiftbits; |
|
615 | + |
|
616 | + return $ipnum === $netnum; |
|
617 | + } |
|
618 | + |
|
619 | + return $trustedProxy === $remoteAddress; |
|
620 | + } |
|
621 | + |
|
622 | + /** |
|
623 | + * Checks if given $remoteAddress matches any entry in the given array $trustedProxies. |
|
624 | + * For details regarding what "match" means, refer to `matchesTrustedProxy`. |
|
625 | + * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise |
|
626 | + */ |
|
627 | + protected function isTrustedProxy($trustedProxies, $remoteAddress) { |
|
628 | + foreach ($trustedProxies as $tp) { |
|
629 | + if ($this->matchesTrustedProxy($tp, $remoteAddress)) { |
|
630 | + return true; |
|
631 | + } |
|
632 | + } |
|
633 | + |
|
634 | + return false; |
|
635 | + } |
|
636 | + |
|
637 | + /** |
|
638 | + * Returns the remote address, if the connection came from a trusted proxy |
|
639 | + * and `forwarded_for_headers` has been configured then the IP address |
|
640 | + * specified in this header will be returned instead. |
|
641 | + * Do always use this instead of $_SERVER['REMOTE_ADDR'] |
|
642 | + * @return string IP address |
|
643 | + */ |
|
644 | + public function getRemoteAddress(): string { |
|
645 | + $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; |
|
646 | + $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); |
|
647 | + |
|
648 | + if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) { |
|
649 | + $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [ |
|
650 | + 'HTTP_X_FORWARDED_FOR' |
|
651 | + // only have one default, so we cannot ship an insecure product out of the box |
|
652 | + ]); |
|
653 | + |
|
654 | + foreach ($forwardedForHeaders as $header) { |
|
655 | + if (isset($this->server[$header])) { |
|
656 | + foreach (explode(',', $this->server[$header]) as $IP) { |
|
657 | + $IP = trim($IP); |
|
658 | + |
|
659 | + // remove brackets from IPv6 addresses |
|
660 | + if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') { |
|
661 | + $IP = substr($IP, 1, -1); |
|
662 | + } |
|
663 | + |
|
664 | + if (filter_var($IP, FILTER_VALIDATE_IP) !== false) { |
|
665 | + return $IP; |
|
666 | + } |
|
667 | + } |
|
668 | + } |
|
669 | + } |
|
670 | + } |
|
671 | + |
|
672 | + return $remoteAddress; |
|
673 | + } |
|
674 | + |
|
675 | + /** |
|
676 | + * Check overwrite condition |
|
677 | + * @param string $type |
|
678 | + * @return bool |
|
679 | + */ |
|
680 | + private function isOverwriteCondition(string $type = ''): bool { |
|
681 | + $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/'; |
|
682 | + $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; |
|
683 | + return $regex === '//' || preg_match($regex, $remoteAddr) === 1 |
|
684 | + || $type !== 'protocol'; |
|
685 | + } |
|
686 | + |
|
687 | + /** |
|
688 | + * Returns the server protocol. It respects one or more reverse proxies servers |
|
689 | + * and load balancers |
|
690 | + * @return string Server protocol (http or https) |
|
691 | + */ |
|
692 | + public function getServerProtocol(): string { |
|
693 | + if ($this->config->getSystemValue('overwriteprotocol') !== '' |
|
694 | + && $this->isOverwriteCondition('protocol')) { |
|
695 | + return $this->config->getSystemValue('overwriteprotocol'); |
|
696 | + } |
|
697 | + |
|
698 | + if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) { |
|
699 | + if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) { |
|
700 | + $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']); |
|
701 | + $proto = strtolower(trim($parts[0])); |
|
702 | + } else { |
|
703 | + $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']); |
|
704 | + } |
|
705 | + |
|
706 | + // Verify that the protocol is always HTTP or HTTPS |
|
707 | + // default to http if an invalid value is provided |
|
708 | + return $proto === 'https' ? 'https' : 'http'; |
|
709 | + } |
|
710 | + |
|
711 | + if (isset($this->server['HTTPS']) |
|
712 | + && $this->server['HTTPS'] !== null |
|
713 | + && $this->server['HTTPS'] !== 'off' |
|
714 | + && $this->server['HTTPS'] !== '') { |
|
715 | + return 'https'; |
|
716 | + } |
|
717 | + |
|
718 | + return 'http'; |
|
719 | + } |
|
720 | + |
|
721 | + /** |
|
722 | + * Returns the used HTTP protocol. |
|
723 | + * |
|
724 | + * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. |
|
725 | + */ |
|
726 | + public function getHttpProtocol(): string { |
|
727 | + $claimedProtocol = $this->server['SERVER_PROTOCOL']; |
|
728 | + |
|
729 | + if (\is_string($claimedProtocol)) { |
|
730 | + $claimedProtocol = strtoupper($claimedProtocol); |
|
731 | + } |
|
732 | + |
|
733 | + $validProtocols = [ |
|
734 | + 'HTTP/1.0', |
|
735 | + 'HTTP/1.1', |
|
736 | + 'HTTP/2', |
|
737 | + ]; |
|
738 | + |
|
739 | + if (\in_array($claimedProtocol, $validProtocols, true)) { |
|
740 | + return $claimedProtocol; |
|
741 | + } |
|
742 | + |
|
743 | + return 'HTTP/1.1'; |
|
744 | + } |
|
745 | + |
|
746 | + /** |
|
747 | + * Returns the request uri, even if the website uses one or more |
|
748 | + * reverse proxies |
|
749 | + * @return string |
|
750 | + */ |
|
751 | + public function getRequestUri(): string { |
|
752 | + $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; |
|
753 | + if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) { |
|
754 | + $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME'])); |
|
755 | + } |
|
756 | + return $uri; |
|
757 | + } |
|
758 | + |
|
759 | + /** |
|
760 | + * Get raw PathInfo from request (not urldecoded) |
|
761 | + * @throws \Exception |
|
762 | + * @return string Path info |
|
763 | + */ |
|
764 | + public function getRawPathInfo(): string { |
|
765 | + $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; |
|
766 | + // remove too many slashes - can be caused by reverse proxy configuration |
|
767 | + $requestUri = preg_replace('%/{2,}%', '/', $requestUri); |
|
768 | + |
|
769 | + // Remove the query string from REQUEST_URI |
|
770 | + if ($pos = strpos($requestUri, '?')) { |
|
771 | + $requestUri = substr($requestUri, 0, $pos); |
|
772 | + } |
|
773 | + |
|
774 | + $scriptName = $this->server['SCRIPT_NAME']; |
|
775 | + $pathInfo = $requestUri; |
|
776 | + |
|
777 | + // strip off the script name's dir and file name |
|
778 | + // FIXME: Sabre does not really belong here |
|
779 | + [$path, $name] = \Sabre\Uri\split($scriptName); |
|
780 | + if (!empty($path)) { |
|
781 | + if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) { |
|
782 | + $pathInfo = substr($pathInfo, \strlen($path)); |
|
783 | + } else { |
|
784 | + throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')"); |
|
785 | + } |
|
786 | + } |
|
787 | + if ($name === null) { |
|
788 | + $name = ''; |
|
789 | + } |
|
790 | + |
|
791 | + if (strpos($pathInfo, '/'.$name) === 0) { |
|
792 | + $pathInfo = substr($pathInfo, \strlen($name) + 1); |
|
793 | + } |
|
794 | + if ($name !== '' && strpos($pathInfo, $name) === 0) { |
|
795 | + $pathInfo = substr($pathInfo, \strlen($name)); |
|
796 | + } |
|
797 | + if ($pathInfo === false || $pathInfo === '/') { |
|
798 | + return ''; |
|
799 | + } else { |
|
800 | + return $pathInfo; |
|
801 | + } |
|
802 | + } |
|
803 | + |
|
804 | + /** |
|
805 | + * Get PathInfo from request |
|
806 | + * @throws \Exception |
|
807 | + * @return string|false Path info or false when not found |
|
808 | + */ |
|
809 | + public function getPathInfo() { |
|
810 | + $pathInfo = $this->getRawPathInfo(); |
|
811 | + // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment |
|
812 | + $pathInfo = rawurldecode($pathInfo); |
|
813 | + $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']); |
|
814 | + |
|
815 | + switch ($encoding) { |
|
816 | + case 'ISO-8859-1': |
|
817 | + $pathInfo = utf8_encode($pathInfo); |
|
818 | + } |
|
819 | + // end copy |
|
820 | + |
|
821 | + return $pathInfo; |
|
822 | + } |
|
823 | + |
|
824 | + /** |
|
825 | + * Returns the script name, even if the website uses one or more |
|
826 | + * reverse proxies |
|
827 | + * @return string the script name |
|
828 | + */ |
|
829 | + public function getScriptName(): string { |
|
830 | + $name = $this->server['SCRIPT_NAME']; |
|
831 | + $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); |
|
832 | + if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) { |
|
833 | + // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous |
|
834 | + $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/'))); |
|
835 | + $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot))); |
|
836 | + $name = '/' . ltrim($overwriteWebRoot . $suburi, '/'); |
|
837 | + } |
|
838 | + return $name; |
|
839 | + } |
|
840 | + |
|
841 | + /** |
|
842 | + * Checks whether the user agent matches a given regex |
|
843 | + * @param array $agent array of agent names |
|
844 | + * @return bool true if at least one of the given agent matches, false otherwise |
|
845 | + */ |
|
846 | + public function isUserAgent(array $agent): bool { |
|
847 | + if (!isset($this->server['HTTP_USER_AGENT'])) { |
|
848 | + return false; |
|
849 | + } |
|
850 | + foreach ($agent as $regex) { |
|
851 | + if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) { |
|
852 | + return true; |
|
853 | + } |
|
854 | + } |
|
855 | + return false; |
|
856 | + } |
|
857 | + |
|
858 | + /** |
|
859 | + * Returns the unverified server host from the headers without checking |
|
860 | + * whether it is a trusted domain |
|
861 | + * @return string Server host |
|
862 | + */ |
|
863 | + public function getInsecureServerHost(): string { |
|
864 | + if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) { |
|
865 | + return $this->getOverwriteHost(); |
|
866 | + } |
|
867 | + |
|
868 | + $host = 'localhost'; |
|
869 | + if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) { |
|
870 | + if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) { |
|
871 | + $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']); |
|
872 | + $host = trim(current($parts)); |
|
873 | + } else { |
|
874 | + $host = $this->server['HTTP_X_FORWARDED_HOST']; |
|
875 | + } |
|
876 | + } else { |
|
877 | + if (isset($this->server['HTTP_HOST'])) { |
|
878 | + $host = $this->server['HTTP_HOST']; |
|
879 | + } elseif (isset($this->server['SERVER_NAME'])) { |
|
880 | + $host = $this->server['SERVER_NAME']; |
|
881 | + } |
|
882 | + } |
|
883 | + |
|
884 | + return $host; |
|
885 | + } |
|
886 | + |
|
887 | + |
|
888 | + /** |
|
889 | + * Returns the server host from the headers, or the first configured |
|
890 | + * trusted domain if the host isn't in the trusted list |
|
891 | + * @return string Server host |
|
892 | + */ |
|
893 | + public function getServerHost(): string { |
|
894 | + // overwritehost is always trusted |
|
895 | + $host = $this->getOverwriteHost(); |
|
896 | + if ($host !== null) { |
|
897 | + return $host; |
|
898 | + } |
|
899 | + |
|
900 | + // get the host from the headers |
|
901 | + $host = $this->getInsecureServerHost(); |
|
902 | + |
|
903 | + // Verify that the host is a trusted domain if the trusted domains |
|
904 | + // are defined |
|
905 | + // If no trusted domain is provided the first trusted domain is returned |
|
906 | + $trustedDomainHelper = new TrustedDomainHelper($this->config); |
|
907 | + if ($trustedDomainHelper->isTrustedDomain($host)) { |
|
908 | + return $host; |
|
909 | + } |
|
910 | + |
|
911 | + $trustedList = (array)$this->config->getSystemValue('trusted_domains', []); |
|
912 | + if (count($trustedList) > 0) { |
|
913 | + return reset($trustedList); |
|
914 | + } |
|
915 | + |
|
916 | + return ''; |
|
917 | + } |
|
918 | + |
|
919 | + /** |
|
920 | + * Returns the overwritehost setting from the config if set and |
|
921 | + * if the overwrite condition is met |
|
922 | + * @return string|null overwritehost value or null if not defined or the defined condition |
|
923 | + * isn't met |
|
924 | + */ |
|
925 | + private function getOverwriteHost() { |
|
926 | + if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) { |
|
927 | + return $this->config->getSystemValue('overwritehost'); |
|
928 | + } |
|
929 | + return null; |
|
930 | + } |
|
931 | + |
|
932 | + private function fromTrustedProxy(): bool { |
|
933 | + $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; |
|
934 | + $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); |
|
935 | + |
|
936 | + return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress); |
|
937 | + } |
|
938 | 938 | } |
@@ -50,185 +50,185 @@ |
||
50 | 50 | */ |
51 | 51 | class App { |
52 | 52 | |
53 | - /** @var string[] */ |
|
54 | - private static $nameSpaceCache = []; |
|
55 | - |
|
56 | - /** |
|
57 | - * Turns an app id into a namespace by either reading the appinfo.xml's |
|
58 | - * namespace tag or uppercasing the appid's first letter |
|
59 | - * @param string $appId the app id |
|
60 | - * @param string $topNamespace the namespace which should be prepended to |
|
61 | - * the transformed app id, defaults to OCA\ |
|
62 | - * @return string the starting namespace for the app |
|
63 | - */ |
|
64 | - public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string { |
|
65 | - // Hit the cache! |
|
66 | - if (isset(self::$nameSpaceCache[$appId])) { |
|
67 | - return $topNamespace . self::$nameSpaceCache[$appId]; |
|
68 | - } |
|
69 | - |
|
70 | - $appInfo = \OC_App::getAppInfo($appId); |
|
71 | - if (isset($appInfo['namespace'])) { |
|
72 | - self::$nameSpaceCache[$appId] = trim($appInfo['namespace']); |
|
73 | - } else { |
|
74 | - if ($appId !== 'spreed') { |
|
75 | - // if the tag is not found, fall back to uppercasing the first letter |
|
76 | - self::$nameSpaceCache[$appId] = ucfirst($appId); |
|
77 | - } else { |
|
78 | - // For the Talk app (appid spreed) the above fallback doesn't work. |
|
79 | - // This leads to a problem when trying to install it freshly, |
|
80 | - // because the apps namespace is already registered before the |
|
81 | - // app is downloaded from the appstore, because of the hackish |
|
82 | - // global route index.php/call/{token} which is registered via |
|
83 | - // the core/routes.php so it does not have the app namespace. |
|
84 | - // @ref https://github.com/nextcloud/server/pull/19433 |
|
85 | - self::$nameSpaceCache[$appId] = 'Talk'; |
|
86 | - } |
|
87 | - } |
|
88 | - |
|
89 | - return $topNamespace . self::$nameSpaceCache[$appId]; |
|
90 | - } |
|
91 | - |
|
92 | - public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string { |
|
93 | - if (strpos($className, $topNamespace) !== 0) { |
|
94 | - return null; |
|
95 | - } |
|
96 | - |
|
97 | - foreach (self::$nameSpaceCache as $appId => $namespace) { |
|
98 | - if (strpos($className, $topNamespace . $namespace . '\\') === 0) { |
|
99 | - return $appId; |
|
100 | - } |
|
101 | - } |
|
102 | - |
|
103 | - return null; |
|
104 | - } |
|
105 | - |
|
106 | - |
|
107 | - /** |
|
108 | - * Shortcut for calling a controller method and printing the result |
|
109 | - * @param string $controllerName the name of the controller under which it is |
|
110 | - * stored in the DI container |
|
111 | - * @param string $methodName the method that you want to call |
|
112 | - * @param DIContainer $container an instance of a pimple container. |
|
113 | - * @param array $urlParams list of URL parameters (optional) |
|
114 | - * @throws HintException |
|
115 | - */ |
|
116 | - public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) { |
|
117 | - if (!is_null($urlParams)) { |
|
118 | - /** @var Request $request */ |
|
119 | - $request = $container->query(IRequest::class); |
|
120 | - $request->setUrlParameters($urlParams); |
|
121 | - } elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) { |
|
122 | - /** @var Request $request */ |
|
123 | - $request = $container->query(IRequest::class); |
|
124 | - $request->setUrlParameters($container['urlParams']); |
|
125 | - } |
|
126 | - $appName = $container['AppName']; |
|
127 | - |
|
128 | - // first try $controllerName then go for \OCA\AppName\Controller\$controllerName |
|
129 | - try { |
|
130 | - $controller = $container->query($controllerName); |
|
131 | - } catch (QueryException $e) { |
|
132 | - if (strpos($controllerName, '\\Controller\\') !== false) { |
|
133 | - // This is from a global registered app route that is not enabled. |
|
134 | - [/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3); |
|
135 | - throw new HintException('App ' . strtolower($app) . ' is not enabled'); |
|
136 | - } |
|
137 | - |
|
138 | - if ($appName === 'core') { |
|
139 | - $appNameSpace = 'OC\\Core'; |
|
140 | - } else { |
|
141 | - $appNameSpace = self::buildAppNamespace($appName); |
|
142 | - } |
|
143 | - $controllerName = $appNameSpace . '\\Controller\\' . $controllerName; |
|
144 | - $controller = $container->query($controllerName); |
|
145 | - } |
|
146 | - |
|
147 | - // initialize the dispatcher and run all the middleware before the controller |
|
148 | - /** @var Dispatcher $dispatcher */ |
|
149 | - $dispatcher = $container['Dispatcher']; |
|
150 | - |
|
151 | - [ |
|
152 | - $httpHeaders, |
|
153 | - $responseHeaders, |
|
154 | - $responseCookies, |
|
155 | - $output, |
|
156 | - $response |
|
157 | - ] = $dispatcher->dispatch($controller, $methodName); |
|
158 | - |
|
159 | - $io = $container[IOutput::class]; |
|
160 | - |
|
161 | - if (!is_null($httpHeaders)) { |
|
162 | - $io->setHeader($httpHeaders); |
|
163 | - } |
|
164 | - |
|
165 | - foreach ($responseHeaders as $name => $value) { |
|
166 | - $io->setHeader($name . ': ' . $value); |
|
167 | - } |
|
168 | - |
|
169 | - foreach ($responseCookies as $name => $value) { |
|
170 | - $expireDate = null; |
|
171 | - if ($value['expireDate'] instanceof \DateTime) { |
|
172 | - $expireDate = $value['expireDate']->getTimestamp(); |
|
173 | - } |
|
174 | - $sameSite = $value['sameSite'] ?? 'Lax'; |
|
175 | - |
|
176 | - $io->setCookie( |
|
177 | - $name, |
|
178 | - $value['value'], |
|
179 | - $expireDate, |
|
180 | - $container->getServer()->getWebRoot(), |
|
181 | - null, |
|
182 | - $container->getServer()->getRequest()->getServerProtocol() === 'https', |
|
183 | - true, |
|
184 | - $sameSite |
|
185 | - ); |
|
186 | - } |
|
187 | - |
|
188 | - /* |
|
53 | + /** @var string[] */ |
|
54 | + private static $nameSpaceCache = []; |
|
55 | + |
|
56 | + /** |
|
57 | + * Turns an app id into a namespace by either reading the appinfo.xml's |
|
58 | + * namespace tag or uppercasing the appid's first letter |
|
59 | + * @param string $appId the app id |
|
60 | + * @param string $topNamespace the namespace which should be prepended to |
|
61 | + * the transformed app id, defaults to OCA\ |
|
62 | + * @return string the starting namespace for the app |
|
63 | + */ |
|
64 | + public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string { |
|
65 | + // Hit the cache! |
|
66 | + if (isset(self::$nameSpaceCache[$appId])) { |
|
67 | + return $topNamespace . self::$nameSpaceCache[$appId]; |
|
68 | + } |
|
69 | + |
|
70 | + $appInfo = \OC_App::getAppInfo($appId); |
|
71 | + if (isset($appInfo['namespace'])) { |
|
72 | + self::$nameSpaceCache[$appId] = trim($appInfo['namespace']); |
|
73 | + } else { |
|
74 | + if ($appId !== 'spreed') { |
|
75 | + // if the tag is not found, fall back to uppercasing the first letter |
|
76 | + self::$nameSpaceCache[$appId] = ucfirst($appId); |
|
77 | + } else { |
|
78 | + // For the Talk app (appid spreed) the above fallback doesn't work. |
|
79 | + // This leads to a problem when trying to install it freshly, |
|
80 | + // because the apps namespace is already registered before the |
|
81 | + // app is downloaded from the appstore, because of the hackish |
|
82 | + // global route index.php/call/{token} which is registered via |
|
83 | + // the core/routes.php so it does not have the app namespace. |
|
84 | + // @ref https://github.com/nextcloud/server/pull/19433 |
|
85 | + self::$nameSpaceCache[$appId] = 'Talk'; |
|
86 | + } |
|
87 | + } |
|
88 | + |
|
89 | + return $topNamespace . self::$nameSpaceCache[$appId]; |
|
90 | + } |
|
91 | + |
|
92 | + public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string { |
|
93 | + if (strpos($className, $topNamespace) !== 0) { |
|
94 | + return null; |
|
95 | + } |
|
96 | + |
|
97 | + foreach (self::$nameSpaceCache as $appId => $namespace) { |
|
98 | + if (strpos($className, $topNamespace . $namespace . '\\') === 0) { |
|
99 | + return $appId; |
|
100 | + } |
|
101 | + } |
|
102 | + |
|
103 | + return null; |
|
104 | + } |
|
105 | + |
|
106 | + |
|
107 | + /** |
|
108 | + * Shortcut for calling a controller method and printing the result |
|
109 | + * @param string $controllerName the name of the controller under which it is |
|
110 | + * stored in the DI container |
|
111 | + * @param string $methodName the method that you want to call |
|
112 | + * @param DIContainer $container an instance of a pimple container. |
|
113 | + * @param array $urlParams list of URL parameters (optional) |
|
114 | + * @throws HintException |
|
115 | + */ |
|
116 | + public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) { |
|
117 | + if (!is_null($urlParams)) { |
|
118 | + /** @var Request $request */ |
|
119 | + $request = $container->query(IRequest::class); |
|
120 | + $request->setUrlParameters($urlParams); |
|
121 | + } elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) { |
|
122 | + /** @var Request $request */ |
|
123 | + $request = $container->query(IRequest::class); |
|
124 | + $request->setUrlParameters($container['urlParams']); |
|
125 | + } |
|
126 | + $appName = $container['AppName']; |
|
127 | + |
|
128 | + // first try $controllerName then go for \OCA\AppName\Controller\$controllerName |
|
129 | + try { |
|
130 | + $controller = $container->query($controllerName); |
|
131 | + } catch (QueryException $e) { |
|
132 | + if (strpos($controllerName, '\\Controller\\') !== false) { |
|
133 | + // This is from a global registered app route that is not enabled. |
|
134 | + [/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3); |
|
135 | + throw new HintException('App ' . strtolower($app) . ' is not enabled'); |
|
136 | + } |
|
137 | + |
|
138 | + if ($appName === 'core') { |
|
139 | + $appNameSpace = 'OC\\Core'; |
|
140 | + } else { |
|
141 | + $appNameSpace = self::buildAppNamespace($appName); |
|
142 | + } |
|
143 | + $controllerName = $appNameSpace . '\\Controller\\' . $controllerName; |
|
144 | + $controller = $container->query($controllerName); |
|
145 | + } |
|
146 | + |
|
147 | + // initialize the dispatcher and run all the middleware before the controller |
|
148 | + /** @var Dispatcher $dispatcher */ |
|
149 | + $dispatcher = $container['Dispatcher']; |
|
150 | + |
|
151 | + [ |
|
152 | + $httpHeaders, |
|
153 | + $responseHeaders, |
|
154 | + $responseCookies, |
|
155 | + $output, |
|
156 | + $response |
|
157 | + ] = $dispatcher->dispatch($controller, $methodName); |
|
158 | + |
|
159 | + $io = $container[IOutput::class]; |
|
160 | + |
|
161 | + if (!is_null($httpHeaders)) { |
|
162 | + $io->setHeader($httpHeaders); |
|
163 | + } |
|
164 | + |
|
165 | + foreach ($responseHeaders as $name => $value) { |
|
166 | + $io->setHeader($name . ': ' . $value); |
|
167 | + } |
|
168 | + |
|
169 | + foreach ($responseCookies as $name => $value) { |
|
170 | + $expireDate = null; |
|
171 | + if ($value['expireDate'] instanceof \DateTime) { |
|
172 | + $expireDate = $value['expireDate']->getTimestamp(); |
|
173 | + } |
|
174 | + $sameSite = $value['sameSite'] ?? 'Lax'; |
|
175 | + |
|
176 | + $io->setCookie( |
|
177 | + $name, |
|
178 | + $value['value'], |
|
179 | + $expireDate, |
|
180 | + $container->getServer()->getWebRoot(), |
|
181 | + null, |
|
182 | + $container->getServer()->getRequest()->getServerProtocol() === 'https', |
|
183 | + true, |
|
184 | + $sameSite |
|
185 | + ); |
|
186 | + } |
|
187 | + |
|
188 | + /* |
|
189 | 189 | * Status 204 does not have a body and no Content Length |
190 | 190 | * Status 304 does not have a body and does not need a Content Length |
191 | 191 | * https://tools.ietf.org/html/rfc7230#section-3.3 |
192 | 192 | * https://tools.ietf.org/html/rfc7230#section-3.3.2 |
193 | 193 | */ |
194 | - $emptyResponse = false; |
|
195 | - if (preg_match('/^HTTP\/\d\.\d (\d{3}) .*$/', $httpHeaders, $matches)) { |
|
196 | - $status = (int)$matches[1]; |
|
197 | - if ($status === Http::STATUS_NO_CONTENT || $status === Http::STATUS_NOT_MODIFIED) { |
|
198 | - $emptyResponse = true; |
|
199 | - } |
|
200 | - } |
|
201 | - |
|
202 | - if (!$emptyResponse) { |
|
203 | - if ($response instanceof ICallbackResponse) { |
|
204 | - $response->callback($io); |
|
205 | - } elseif (!is_null($output)) { |
|
206 | - $io->setHeader('Content-Length: ' . strlen($output)); |
|
207 | - $io->setOutput($output); |
|
208 | - } |
|
209 | - } |
|
210 | - } |
|
211 | - |
|
212 | - /** |
|
213 | - * Shortcut for calling a controller method and printing the result. |
|
214 | - * Similar to App:main except that no headers will be sent. |
|
215 | - * This should be used for example when registering sections via |
|
216 | - * \OC\AppFramework\Core\API::registerAdmin() |
|
217 | - * |
|
218 | - * @param string $controllerName the name of the controller under which it is |
|
219 | - * stored in the DI container |
|
220 | - * @param string $methodName the method that you want to call |
|
221 | - * @param array $urlParams an array with variables extracted from the routes |
|
222 | - * @param DIContainer $container an instance of a pimple container. |
|
223 | - */ |
|
224 | - public static function part(string $controllerName, string $methodName, array $urlParams, |
|
225 | - DIContainer $container) { |
|
226 | - $container['urlParams'] = $urlParams; |
|
227 | - $controller = $container[$controllerName]; |
|
228 | - |
|
229 | - $dispatcher = $container['Dispatcher']; |
|
230 | - |
|
231 | - [, , $output] = $dispatcher->dispatch($controller, $methodName); |
|
232 | - return $output; |
|
233 | - } |
|
194 | + $emptyResponse = false; |
|
195 | + if (preg_match('/^HTTP\/\d\.\d (\d{3}) .*$/', $httpHeaders, $matches)) { |
|
196 | + $status = (int)$matches[1]; |
|
197 | + if ($status === Http::STATUS_NO_CONTENT || $status === Http::STATUS_NOT_MODIFIED) { |
|
198 | + $emptyResponse = true; |
|
199 | + } |
|
200 | + } |
|
201 | + |
|
202 | + if (!$emptyResponse) { |
|
203 | + if ($response instanceof ICallbackResponse) { |
|
204 | + $response->callback($io); |
|
205 | + } elseif (!is_null($output)) { |
|
206 | + $io->setHeader('Content-Length: ' . strlen($output)); |
|
207 | + $io->setOutput($output); |
|
208 | + } |
|
209 | + } |
|
210 | + } |
|
211 | + |
|
212 | + /** |
|
213 | + * Shortcut for calling a controller method and printing the result. |
|
214 | + * Similar to App:main except that no headers will be sent. |
|
215 | + * This should be used for example when registering sections via |
|
216 | + * \OC\AppFramework\Core\API::registerAdmin() |
|
217 | + * |
|
218 | + * @param string $controllerName the name of the controller under which it is |
|
219 | + * stored in the DI container |
|
220 | + * @param string $methodName the method that you want to call |
|
221 | + * @param array $urlParams an array with variables extracted from the routes |
|
222 | + * @param DIContainer $container an instance of a pimple container. |
|
223 | + */ |
|
224 | + public static function part(string $controllerName, string $methodName, array $urlParams, |
|
225 | + DIContainer $container) { |
|
226 | + $container['urlParams'] = $urlParams; |
|
227 | + $controller = $container[$controllerName]; |
|
228 | + |
|
229 | + $dispatcher = $container['Dispatcher']; |
|
230 | + |
|
231 | + [, , $output] = $dispatcher->dispatch($controller, $methodName); |
|
232 | + return $output; |
|
233 | + } |
|
234 | 234 | } |
@@ -64,7 +64,7 @@ discard block |
||
64 | 64 | public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string { |
65 | 65 | // Hit the cache! |
66 | 66 | if (isset(self::$nameSpaceCache[$appId])) { |
67 | - return $topNamespace . self::$nameSpaceCache[$appId]; |
|
67 | + return $topNamespace.self::$nameSpaceCache[$appId]; |
|
68 | 68 | } |
69 | 69 | |
70 | 70 | $appInfo = \OC_App::getAppInfo($appId); |
@@ -86,7 +86,7 @@ discard block |
||
86 | 86 | } |
87 | 87 | } |
88 | 88 | |
89 | - return $topNamespace . self::$nameSpaceCache[$appId]; |
|
89 | + return $topNamespace.self::$nameSpaceCache[$appId]; |
|
90 | 90 | } |
91 | 91 | |
92 | 92 | public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string { |
@@ -95,7 +95,7 @@ discard block |
||
95 | 95 | } |
96 | 96 | |
97 | 97 | foreach (self::$nameSpaceCache as $appId => $namespace) { |
98 | - if (strpos($className, $topNamespace . $namespace . '\\') === 0) { |
|
98 | + if (strpos($className, $topNamespace.$namespace.'\\') === 0) { |
|
99 | 99 | return $appId; |
100 | 100 | } |
101 | 101 | } |
@@ -132,7 +132,7 @@ discard block |
||
132 | 132 | if (strpos($controllerName, '\\Controller\\') !== false) { |
133 | 133 | // This is from a global registered app route that is not enabled. |
134 | 134 | [/*OC(A)*/, $app, /* Controller/Name*/] = explode('\\', $controllerName, 3); |
135 | - throw new HintException('App ' . strtolower($app) . ' is not enabled'); |
|
135 | + throw new HintException('App '.strtolower($app).' is not enabled'); |
|
136 | 136 | } |
137 | 137 | |
138 | 138 | if ($appName === 'core') { |
@@ -140,7 +140,7 @@ discard block |
||
140 | 140 | } else { |
141 | 141 | $appNameSpace = self::buildAppNamespace($appName); |
142 | 142 | } |
143 | - $controllerName = $appNameSpace . '\\Controller\\' . $controllerName; |
|
143 | + $controllerName = $appNameSpace.'\\Controller\\'.$controllerName; |
|
144 | 144 | $controller = $container->query($controllerName); |
145 | 145 | } |
146 | 146 | |
@@ -163,7 +163,7 @@ discard block |
||
163 | 163 | } |
164 | 164 | |
165 | 165 | foreach ($responseHeaders as $name => $value) { |
166 | - $io->setHeader($name . ': ' . $value); |
|
166 | + $io->setHeader($name.': '.$value); |
|
167 | 167 | } |
168 | 168 | |
169 | 169 | foreach ($responseCookies as $name => $value) { |
@@ -193,7 +193,7 @@ discard block |
||
193 | 193 | */ |
194 | 194 | $emptyResponse = false; |
195 | 195 | if (preg_match('/^HTTP\/\d\.\d (\d{3}) .*$/', $httpHeaders, $matches)) { |
196 | - $status = (int)$matches[1]; |
|
196 | + $status = (int) $matches[1]; |
|
197 | 197 | if ($status === Http::STATUS_NO_CONTENT || $status === Http::STATUS_NOT_MODIFIED) { |
198 | 198 | $emptyResponse = true; |
199 | 199 | } |
@@ -203,7 +203,7 @@ discard block |
||
203 | 203 | if ($response instanceof ICallbackResponse) { |
204 | 204 | $response->callback($io); |
205 | 205 | } elseif (!is_null($output)) { |
206 | - $io->setHeader('Content-Length: ' . strlen($output)); |
|
206 | + $io->setHeader('Content-Length: '.strlen($output)); |
|
207 | 207 | $io->setOutput($output); |
208 | 208 | } |
209 | 209 | } |
@@ -228,7 +228,7 @@ discard block |
||
228 | 228 | |
229 | 229 | $dispatcher = $container['Dispatcher']; |
230 | 230 | |
231 | - [, , $output] = $dispatcher->dispatch($controller, $methodName); |
|
231 | + [,, $output] = $dispatcher->dispatch($controller, $methodName); |
|
232 | 232 | return $output; |
233 | 233 | } |
234 | 234 | } |