Completed
Pull Request — master (#8060)
by Morris
101:04 queued 81:47
created
apps/files_external/lib/Lib/Storage/AmazonS3.php 2 patches
Doc Comments   +10 added lines, -1 removed lines patch added patch discarded remove patch
@@ -85,6 +85,9 @@  discard block
 block discarded – undo
85 85
 		return $path === '.';
86 86
 	}
87 87
 
88
+	/**
89
+	 * @param string $path
90
+	 */
88 91
 	private function cleanKey($path) {
89 92
 		if ($this->isRoot($path)) {
90 93
 			return '/';
@@ -108,7 +111,7 @@  discard block
 block discarded – undo
108 111
 	}
109 112
 
110 113
 	/**
111
-	 * @param $key
114
+	 * @param string $key
112 115
 	 * @return Result|boolean
113 116
 	 */
114 117
 	private function headObject($key) {
@@ -234,6 +237,9 @@  discard block
 block discarded – undo
234 237
 		}
235 238
 	}
236 239
 
240
+	/**
241
+	 * @param string $path
242
+	 */
237 243
 	private function batchDelete($path = null) {
238 244
 		$params = array(
239 245
 			'Bucket' => $this->bucket
@@ -579,6 +585,9 @@  discard block
 block discarded – undo
579 585
 		return $this->id;
580 586
 	}
581 587
 
588
+	/**
589
+	 * @param string $path
590
+	 */
582 591
 	public function writeBack($tmpFile, $path) {
583 592
 		try {
584 593
 			$source = fopen($tmpFile, 'r');
Please login to merge, or discard this patch.
Indentation   +554 added lines, -554 removed lines patch added patch discarded remove patch
@@ -46,559 +46,559 @@
 block discarded – undo
46 46
 use OCP\Constants;
47 47
 
48 48
 class AmazonS3 extends \OC\Files\Storage\Common {
49
-	use S3ConnectionTrait;
50
-	use S3ObjectTrait;
51
-
52
-	public function needsPartFile() {
53
-		return false;
54
-	}
55
-
56
-	/**
57
-	 * @var int in seconds
58
-	 */
59
-	private $rescanDelay = 10;
60
-
61
-	/** @var CappedMemoryCache|Result[] */
62
-	private $objectCache;
63
-
64
-	public function __construct($parameters) {
65
-		parent::__construct($parameters);
66
-		$this->parseParams($parameters);
67
-		$this->objectCache = new CappedMemoryCache();
68
-	}
69
-
70
-	/**
71
-	 * @param string $path
72
-	 * @return string correctly encoded path
73
-	 */
74
-	private function normalizePath($path) {
75
-		$path = trim($path, '/');
76
-
77
-		if (!$path) {
78
-			$path = '.';
79
-		}
80
-
81
-		return $path;
82
-	}
83
-
84
-	private function isRoot($path) {
85
-		return $path === '.';
86
-	}
87
-
88
-	private function cleanKey($path) {
89
-		if ($this->isRoot($path)) {
90
-			return '/';
91
-		}
92
-		return $path;
93
-	}
94
-
95
-	private function clearCache() {
96
-		$this->objectCache = new CappedMemoryCache();
97
-	}
98
-
99
-	private function invalidateCache($key) {
100
-		unset($this->objectCache[$key]);
101
-		$keys = array_keys($this->objectCache->getData());
102
-		$keyLength = strlen($key);
103
-		foreach ($keys as $existingKey) {
104
-			if (substr($existingKey, 0, $keyLength) === $keys) {
105
-				unset($this->objectCache[$existingKey]);
106
-			}
107
-		}
108
-	}
109
-
110
-	/**
111
-	 * @param $key
112
-	 * @return Result|boolean
113
-	 */
114
-	private function headObject($key) {
115
-		if (!isset($this->objectCache[$key])) {
116
-			try {
117
-				$this->objectCache[$key] = $this->getConnection()->headObject(array(
118
-					'Bucket' => $this->bucket,
119
-					'Key' => $key
120
-				));
121
-			} catch (S3Exception $e) {
122
-				if ($e->getStatusCode() >= 500) {
123
-					throw $e;
124
-				}
125
-				$this->objectCache[$key] = false;
126
-			}
127
-		}
128
-
129
-		return $this->objectCache[$key];
130
-	}
131
-
132
-	/**
133
-	 * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
134
-	 * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
135
-	 *
136
-	 * @param array $params
137
-	 */
138
-	public function updateLegacyId(array $params) {
139
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
140
-
141
-		// find by old id or bucket
142
-		$stmt = \OC::$server->getDatabaseConnection()->prepare(
143
-			'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
144
-		);
145
-		$stmt->execute(array($oldId, $this->id));
146
-		while ($row = $stmt->fetch()) {
147
-			$storages[$row['id']] = $row['numeric_id'];
148
-		}
149
-
150
-		if (isset($storages[$this->id]) && isset($storages[$oldId])) {
151
-			// if both ids exist, delete the old storage and corresponding filecache entries
152
-			\OC\Files\Cache\Storage::remove($oldId);
153
-		} else if (isset($storages[$oldId])) {
154
-			// if only the old id exists do an update
155
-			$stmt = \OC::$server->getDatabaseConnection()->prepare(
156
-				'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
157
-			);
158
-			$stmt->execute(array($this->id, $oldId));
159
-		}
160
-		// only the bucket based id may exist, do nothing
161
-	}
162
-
163
-	/**
164
-	 * Remove a file or folder
165
-	 *
166
-	 * @param string $path
167
-	 * @return bool
168
-	 */
169
-	protected function remove($path) {
170
-		// remember fileType to reduce http calls
171
-		$fileType = $this->filetype($path);
172
-		if ($fileType === 'dir') {
173
-			return $this->rmdir($path);
174
-		} else if ($fileType === 'file') {
175
-			return $this->unlink($path);
176
-		} else {
177
-			return false;
178
-		}
179
-	}
180
-
181
-	public function mkdir($path) {
182
-		$path = $this->normalizePath($path);
183
-
184
-		if ($this->is_dir($path)) {
185
-			return false;
186
-		}
187
-
188
-		try {
189
-			$this->getConnection()->putObject(array(
190
-				'Bucket' => $this->bucket,
191
-				'Key' => $path . '/',
192
-				'Body' => '',
193
-				'ContentType' => 'httpd/unix-directory'
194
-			));
195
-			$this->testTimeout();
196
-		} catch (S3Exception $e) {
197
-			\OCP\Util::logException('files_external', $e);
198
-			return false;
199
-		}
200
-
201
-		$this->invalidateCache($path);
202
-
203
-		return true;
204
-	}
205
-
206
-	public function file_exists($path) {
207
-		return $this->filetype($path) !== false;
208
-	}
209
-
210
-
211
-	public function rmdir($path) {
212
-		$path = $this->normalizePath($path);
213
-
214
-		if ($this->isRoot($path)) {
215
-			return $this->clearBucket();
216
-		}
217
-
218
-		if (!$this->file_exists($path)) {
219
-			return false;
220
-		}
221
-
222
-		$this->invalidateCache($path);
223
-		return $this->batchDelete($path);
224
-	}
225
-
226
-	protected function clearBucket() {
227
-		$this->clearCache();
228
-		try {
229
-			$this->getConnection()->clearBucket($this->bucket);
230
-			return true;
231
-			// clearBucket() is not working with Ceph, so if it fails we try the slower approach
232
-		} catch (\Exception $e) {
233
-			return $this->batchDelete();
234
-		}
235
-	}
236
-
237
-	private function batchDelete($path = null) {
238
-		$params = array(
239
-			'Bucket' => $this->bucket
240
-		);
241
-		if ($path !== null) {
242
-			$params['Prefix'] = $path . '/';
243
-		}
244
-		try {
245
-			// Since there are no real directories on S3, we need
246
-			// to delete all objects prefixed with the path.
247
-			do {
248
-				// instead of the iterator, manually loop over the list ...
249
-				$objects = $this->getConnection()->listObjects($params);
250
-				// ... so we can delete the files in batches
251
-				$this->getConnection()->deleteObjects(array(
252
-					'Bucket' => $this->bucket,
253
-					'Objects' => $objects['Contents']
254
-				));
255
-				$this->testTimeout();
256
-				// we reached the end when the list is no longer truncated
257
-			} while ($objects['IsTruncated']);
258
-		} catch (S3Exception $e) {
259
-			\OCP\Util::logException('files_external', $e);
260
-			return false;
261
-		}
262
-		return true;
263
-	}
264
-
265
-	public function opendir($path) {
266
-		$path = $this->normalizePath($path);
267
-
268
-		if ($this->isRoot($path)) {
269
-			$path = '';
270
-		} else {
271
-			$path .= '/';
272
-		}
273
-
274
-		try {
275
-			$files = array();
276
-			$results = $this->getConnection()->getPaginator('ListObjects', [
277
-				'Bucket' => $this->bucket,
278
-				'Delimiter' => '/',
279
-				'Prefix' => $path,
280
-			]);
281
-
282
-			foreach ($results as $result) {
283
-				// sub folders
284
-				if (is_array($result['CommonPrefixes'])) {
285
-					foreach ($result['CommonPrefixes'] as $prefix) {
286
-						$files[] = substr(trim($prefix['Prefix'], '/'), strlen($path));
287
-					}
288
-				}
289
-				foreach ($result['Contents'] as $object) {
290
-					if (isset($object['Key']) && $object['Key'] === $path) {
291
-						// it's the directory itself, skip
292
-						continue;
293
-					}
294
-					$file = basename(
295
-						isset($object['Key']) ? $object['Key'] : $object['Prefix']
296
-					);
297
-					$files[] = $file;
298
-				}
299
-			}
300
-
301
-			return IteratorDirectory::wrap($files);
302
-		} catch (S3Exception $e) {
303
-			\OCP\Util::logException('files_external', $e);
304
-			return false;
305
-		}
306
-	}
307
-
308
-	public function stat($path) {
309
-		$path = $this->normalizePath($path);
310
-
311
-		try {
312
-			$stat = array();
313
-			if ($this->is_dir($path)) {
314
-				//folders don't really exist
315
-				$stat['size'] = -1; //unknown
316
-				$stat['mtime'] = time() - $this->rescanDelay * 1000;
317
-			} else {
318
-				$result = $this->headObject($path);
319
-
320
-				$stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0;
321
-				if (isset($result['Metadata']['lastmodified'])) {
322
-					$stat['mtime'] = strtotime($result['Metadata']['lastmodified']);
323
-				} else {
324
-					$stat['mtime'] = strtotime($result['LastModified']);
325
-				}
326
-			}
327
-			$stat['atime'] = time();
328
-
329
-			return $stat;
330
-		} catch (S3Exception $e) {
331
-			\OCP\Util::logException('files_external', $e);
332
-			return false;
333
-		}
334
-	}
335
-
336
-	public function is_dir($path) {
337
-		$path = $this->normalizePath($path);
338
-		try {
339
-			return $this->isRoot($path) || $this->headObject($path . '/');
340
-		} catch (S3Exception $e) {
341
-			\OCP\Util::logException('files_external', $e);
342
-			return false;
343
-		}
344
-	}
345
-
346
-	public function filetype($path) {
347
-		$path = $this->normalizePath($path);
348
-
349
-		if ($this->isRoot($path)) {
350
-			return 'dir';
351
-		}
352
-
353
-		try {
354
-			if ($this->headObject($path)) {
355
-				return 'file';
356
-			}
357
-			if ($this->headObject($path . '/')) {
358
-				return 'dir';
359
-			}
360
-		} catch (S3Exception $e) {
361
-			\OCP\Util::logException('files_external', $e);
362
-			return false;
363
-		}
364
-
365
-		return false;
366
-	}
367
-
368
-	public function getPermissions($path) {
369
-		$type = $this->filetype($path);
370
-		if (!$type) {
371
-			return 0;
372
-		}
373
-		return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
374
-	}
375
-
376
-	public function unlink($path) {
377
-		$path = $this->normalizePath($path);
378
-
379
-		if ($this->is_dir($path)) {
380
-			return $this->rmdir($path);
381
-		}
382
-
383
-		try {
384
-			$this->deleteObject($path);
385
-			$this->invalidateCache($path);
386
-		} catch (S3Exception $e) {
387
-			\OCP\Util::logException('files_external', $e);
388
-			return false;
389
-		}
390
-
391
-		return true;
392
-	}
393
-
394
-	public function fopen($path, $mode) {
395
-		$path = $this->normalizePath($path);
396
-
397
-		switch ($mode) {
398
-			case 'r':
399
-			case 'rb':
400
-				try {
401
-					return $this->readObject($path);
402
-				} catch (S3Exception $e) {
403
-					\OCP\Util::logException('files_external', $e);
404
-					return false;
405
-				}
406
-			case 'w':
407
-			case 'wb':
408
-				$tmpFile = \OCP\Files::tmpFile();
409
-
410
-				$handle = fopen($tmpFile, 'w');
411
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
412
-					$this->writeBack($tmpFile, $path);
413
-				});
414
-			case 'a':
415
-			case 'ab':
416
-			case 'r+':
417
-			case 'w+':
418
-			case 'wb+':
419
-			case 'a+':
420
-			case 'x':
421
-			case 'x+':
422
-			case 'c':
423
-			case 'c+':
424
-				if (strrpos($path, '.') !== false) {
425
-					$ext = substr($path, strrpos($path, '.'));
426
-				} else {
427
-					$ext = '';
428
-				}
429
-				$tmpFile = \OCP\Files::tmpFile($ext);
430
-				if ($this->file_exists($path)) {
431
-					$source = $this->readObject($path);
432
-					file_put_contents($tmpFile, $source);
433
-				}
434
-
435
-				$handle = fopen($tmpFile, $mode);
436
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
437
-					$this->writeBack($tmpFile, $path);
438
-				});
439
-		}
440
-		return false;
441
-	}
442
-
443
-	public function touch($path, $mtime = null) {
444
-		$path = $this->normalizePath($path);
445
-
446
-		$metadata = array();
447
-		if (is_null($mtime)) {
448
-			$mtime = time();
449
-		}
450
-		$metadata = [
451
-			'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
452
-		];
453
-
454
-		$fileType = $this->filetype($path);
455
-		try {
456
-			if ($fileType !== false) {
457
-				if ($fileType === 'dir' && !$this->isRoot($path)) {
458
-					$path .= '/';
459
-				}
460
-				$this->getConnection()->copyObject([
461
-					'Bucket' => $this->bucket,
462
-					'Key' => $this->cleanKey($path),
463
-					'Metadata' => $metadata,
464
-					'CopySource' => $this->bucket . '/' . $path,
465
-					'MetadataDirective' => 'REPLACE',
466
-				]);
467
-				$this->testTimeout();
468
-			} else {
469
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
470
-				$this->getConnection()->putObject([
471
-					'Bucket' => $this->bucket,
472
-					'Key' => $this->cleanKey($path),
473
-					'Metadata' => $metadata,
474
-					'Body' => '',
475
-					'ContentType' => $mimeType,
476
-					'MetadataDirective' => 'REPLACE',
477
-				]);
478
-				$this->testTimeout();
479
-			}
480
-		} catch (S3Exception $e) {
481
-			\OCP\Util::logException('files_external', $e);
482
-			return false;
483
-		}
484
-
485
-		$this->invalidateCache($path);
486
-		return true;
487
-	}
488
-
489
-	public function copy($path1, $path2) {
490
-		$path1 = $this->normalizePath($path1);
491
-		$path2 = $this->normalizePath($path2);
492
-
493
-		if ($this->is_file($path1)) {
494
-			try {
495
-				$this->getConnection()->copyObject(array(
496
-					'Bucket' => $this->bucket,
497
-					'Key' => $this->cleanKey($path2),
498
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
499
-				));
500
-				$this->testTimeout();
501
-			} catch (S3Exception $e) {
502
-				\OCP\Util::logException('files_external', $e);
503
-				return false;
504
-			}
505
-		} else {
506
-			$this->remove($path2);
507
-
508
-			try {
509
-				$this->getConnection()->copyObject(array(
510
-					'Bucket' => $this->bucket,
511
-					'Key' => $path2 . '/',
512
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
513
-				));
514
-				$this->testTimeout();
515
-			} catch (S3Exception $e) {
516
-				\OCP\Util::logException('files_external', $e);
517
-				return false;
518
-			}
519
-
520
-			$dh = $this->opendir($path1);
521
-			if (is_resource($dh)) {
522
-				while (($file = readdir($dh)) !== false) {
523
-					if (\OC\Files\Filesystem::isIgnoredDir($file)) {
524
-						continue;
525
-					}
526
-
527
-					$source = $path1 . '/' . $file;
528
-					$target = $path2 . '/' . $file;
529
-					$this->copy($source, $target);
530
-				}
531
-			}
532
-		}
533
-
534
-		$this->invalidateCache($path2);
535
-
536
-		return true;
537
-	}
538
-
539
-	public function rename($path1, $path2) {
540
-		$path1 = $this->normalizePath($path1);
541
-		$path2 = $this->normalizePath($path2);
542
-
543
-		if ($this->is_file($path1)) {
544
-
545
-			if ($this->copy($path1, $path2) === false) {
546
-				return false;
547
-			}
548
-
549
-			if ($this->unlink($path1) === false) {
550
-				$this->unlink($path2);
551
-				return false;
552
-			}
553
-		} else {
554
-
555
-			if ($this->copy($path1, $path2) === false) {
556
-				return false;
557
-			}
558
-
559
-			if ($this->rmdir($path1) === false) {
560
-				$this->rmdir($path2);
561
-				return false;
562
-			}
563
-		}
564
-
565
-		return true;
566
-	}
567
-
568
-	public function test() {
569
-		$test = $this->getConnection()->getBucketAcl(array(
570
-			'Bucket' => $this->bucket,
571
-		));
572
-		if (isset($test) && !is_null($test->getPath('Owner/ID'))) {
573
-			return true;
574
-		}
575
-		return false;
576
-	}
577
-
578
-	public function getId() {
579
-		return $this->id;
580
-	}
581
-
582
-	public function writeBack($tmpFile, $path) {
583
-		try {
584
-			$source = fopen($tmpFile, 'r');
585
-			$this->writeObject($path, $source);
586
-			$this->invalidateCache($path);
587
-			fclose($source);
588
-
589
-			unlink($tmpFile);
590
-			return true;
591
-		} catch (S3Exception $e) {
592
-			\OCP\Util::logException('files_external', $e);
593
-			return false;
594
-		}
595
-	}
596
-
597
-	/**
598
-	 * check if curl is installed
599
-	 */
600
-	public static function checkDependencies() {
601
-		return true;
602
-	}
49
+    use S3ConnectionTrait;
50
+    use S3ObjectTrait;
51
+
52
+    public function needsPartFile() {
53
+        return false;
54
+    }
55
+
56
+    /**
57
+     * @var int in seconds
58
+     */
59
+    private $rescanDelay = 10;
60
+
61
+    /** @var CappedMemoryCache|Result[] */
62
+    private $objectCache;
63
+
64
+    public function __construct($parameters) {
65
+        parent::__construct($parameters);
66
+        $this->parseParams($parameters);
67
+        $this->objectCache = new CappedMemoryCache();
68
+    }
69
+
70
+    /**
71
+     * @param string $path
72
+     * @return string correctly encoded path
73
+     */
74
+    private function normalizePath($path) {
75
+        $path = trim($path, '/');
76
+
77
+        if (!$path) {
78
+            $path = '.';
79
+        }
80
+
81
+        return $path;
82
+    }
83
+
84
+    private function isRoot($path) {
85
+        return $path === '.';
86
+    }
87
+
88
+    private function cleanKey($path) {
89
+        if ($this->isRoot($path)) {
90
+            return '/';
91
+        }
92
+        return $path;
93
+    }
94
+
95
+    private function clearCache() {
96
+        $this->objectCache = new CappedMemoryCache();
97
+    }
98
+
99
+    private function invalidateCache($key) {
100
+        unset($this->objectCache[$key]);
101
+        $keys = array_keys($this->objectCache->getData());
102
+        $keyLength = strlen($key);
103
+        foreach ($keys as $existingKey) {
104
+            if (substr($existingKey, 0, $keyLength) === $keys) {
105
+                unset($this->objectCache[$existingKey]);
106
+            }
107
+        }
108
+    }
109
+
110
+    /**
111
+     * @param $key
112
+     * @return Result|boolean
113
+     */
114
+    private function headObject($key) {
115
+        if (!isset($this->objectCache[$key])) {
116
+            try {
117
+                $this->objectCache[$key] = $this->getConnection()->headObject(array(
118
+                    'Bucket' => $this->bucket,
119
+                    'Key' => $key
120
+                ));
121
+            } catch (S3Exception $e) {
122
+                if ($e->getStatusCode() >= 500) {
123
+                    throw $e;
124
+                }
125
+                $this->objectCache[$key] = false;
126
+            }
127
+        }
128
+
129
+        return $this->objectCache[$key];
130
+    }
131
+
132
+    /**
133
+     * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
134
+     * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
135
+     *
136
+     * @param array $params
137
+     */
138
+    public function updateLegacyId(array $params) {
139
+        $oldId = 'amazon::' . $params['key'] . md5($params['secret']);
140
+
141
+        // find by old id or bucket
142
+        $stmt = \OC::$server->getDatabaseConnection()->prepare(
143
+            'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
144
+        );
145
+        $stmt->execute(array($oldId, $this->id));
146
+        while ($row = $stmt->fetch()) {
147
+            $storages[$row['id']] = $row['numeric_id'];
148
+        }
149
+
150
+        if (isset($storages[$this->id]) && isset($storages[$oldId])) {
151
+            // if both ids exist, delete the old storage and corresponding filecache entries
152
+            \OC\Files\Cache\Storage::remove($oldId);
153
+        } else if (isset($storages[$oldId])) {
154
+            // if only the old id exists do an update
155
+            $stmt = \OC::$server->getDatabaseConnection()->prepare(
156
+                'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
157
+            );
158
+            $stmt->execute(array($this->id, $oldId));
159
+        }
160
+        // only the bucket based id may exist, do nothing
161
+    }
162
+
163
+    /**
164
+     * Remove a file or folder
165
+     *
166
+     * @param string $path
167
+     * @return bool
168
+     */
169
+    protected function remove($path) {
170
+        // remember fileType to reduce http calls
171
+        $fileType = $this->filetype($path);
172
+        if ($fileType === 'dir') {
173
+            return $this->rmdir($path);
174
+        } else if ($fileType === 'file') {
175
+            return $this->unlink($path);
176
+        } else {
177
+            return false;
178
+        }
179
+    }
180
+
181
+    public function mkdir($path) {
182
+        $path = $this->normalizePath($path);
183
+
184
+        if ($this->is_dir($path)) {
185
+            return false;
186
+        }
187
+
188
+        try {
189
+            $this->getConnection()->putObject(array(
190
+                'Bucket' => $this->bucket,
191
+                'Key' => $path . '/',
192
+                'Body' => '',
193
+                'ContentType' => 'httpd/unix-directory'
194
+            ));
195
+            $this->testTimeout();
196
+        } catch (S3Exception $e) {
197
+            \OCP\Util::logException('files_external', $e);
198
+            return false;
199
+        }
200
+
201
+        $this->invalidateCache($path);
202
+
203
+        return true;
204
+    }
205
+
206
+    public function file_exists($path) {
207
+        return $this->filetype($path) !== false;
208
+    }
209
+
210
+
211
+    public function rmdir($path) {
212
+        $path = $this->normalizePath($path);
213
+
214
+        if ($this->isRoot($path)) {
215
+            return $this->clearBucket();
216
+        }
217
+
218
+        if (!$this->file_exists($path)) {
219
+            return false;
220
+        }
221
+
222
+        $this->invalidateCache($path);
223
+        return $this->batchDelete($path);
224
+    }
225
+
226
+    protected function clearBucket() {
227
+        $this->clearCache();
228
+        try {
229
+            $this->getConnection()->clearBucket($this->bucket);
230
+            return true;
231
+            // clearBucket() is not working with Ceph, so if it fails we try the slower approach
232
+        } catch (\Exception $e) {
233
+            return $this->batchDelete();
234
+        }
235
+    }
236
+
237
+    private function batchDelete($path = null) {
238
+        $params = array(
239
+            'Bucket' => $this->bucket
240
+        );
241
+        if ($path !== null) {
242
+            $params['Prefix'] = $path . '/';
243
+        }
244
+        try {
245
+            // Since there are no real directories on S3, we need
246
+            // to delete all objects prefixed with the path.
247
+            do {
248
+                // instead of the iterator, manually loop over the list ...
249
+                $objects = $this->getConnection()->listObjects($params);
250
+                // ... so we can delete the files in batches
251
+                $this->getConnection()->deleteObjects(array(
252
+                    'Bucket' => $this->bucket,
253
+                    'Objects' => $objects['Contents']
254
+                ));
255
+                $this->testTimeout();
256
+                // we reached the end when the list is no longer truncated
257
+            } while ($objects['IsTruncated']);
258
+        } catch (S3Exception $e) {
259
+            \OCP\Util::logException('files_external', $e);
260
+            return false;
261
+        }
262
+        return true;
263
+    }
264
+
265
+    public function opendir($path) {
266
+        $path = $this->normalizePath($path);
267
+
268
+        if ($this->isRoot($path)) {
269
+            $path = '';
270
+        } else {
271
+            $path .= '/';
272
+        }
273
+
274
+        try {
275
+            $files = array();
276
+            $results = $this->getConnection()->getPaginator('ListObjects', [
277
+                'Bucket' => $this->bucket,
278
+                'Delimiter' => '/',
279
+                'Prefix' => $path,
280
+            ]);
281
+
282
+            foreach ($results as $result) {
283
+                // sub folders
284
+                if (is_array($result['CommonPrefixes'])) {
285
+                    foreach ($result['CommonPrefixes'] as $prefix) {
286
+                        $files[] = substr(trim($prefix['Prefix'], '/'), strlen($path));
287
+                    }
288
+                }
289
+                foreach ($result['Contents'] as $object) {
290
+                    if (isset($object['Key']) && $object['Key'] === $path) {
291
+                        // it's the directory itself, skip
292
+                        continue;
293
+                    }
294
+                    $file = basename(
295
+                        isset($object['Key']) ? $object['Key'] : $object['Prefix']
296
+                    );
297
+                    $files[] = $file;
298
+                }
299
+            }
300
+
301
+            return IteratorDirectory::wrap($files);
302
+        } catch (S3Exception $e) {
303
+            \OCP\Util::logException('files_external', $e);
304
+            return false;
305
+        }
306
+    }
307
+
308
+    public function stat($path) {
309
+        $path = $this->normalizePath($path);
310
+
311
+        try {
312
+            $stat = array();
313
+            if ($this->is_dir($path)) {
314
+                //folders don't really exist
315
+                $stat['size'] = -1; //unknown
316
+                $stat['mtime'] = time() - $this->rescanDelay * 1000;
317
+            } else {
318
+                $result = $this->headObject($path);
319
+
320
+                $stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0;
321
+                if (isset($result['Metadata']['lastmodified'])) {
322
+                    $stat['mtime'] = strtotime($result['Metadata']['lastmodified']);
323
+                } else {
324
+                    $stat['mtime'] = strtotime($result['LastModified']);
325
+                }
326
+            }
327
+            $stat['atime'] = time();
328
+
329
+            return $stat;
330
+        } catch (S3Exception $e) {
331
+            \OCP\Util::logException('files_external', $e);
332
+            return false;
333
+        }
334
+    }
335
+
336
+    public function is_dir($path) {
337
+        $path = $this->normalizePath($path);
338
+        try {
339
+            return $this->isRoot($path) || $this->headObject($path . '/');
340
+        } catch (S3Exception $e) {
341
+            \OCP\Util::logException('files_external', $e);
342
+            return false;
343
+        }
344
+    }
345
+
346
+    public function filetype($path) {
347
+        $path = $this->normalizePath($path);
348
+
349
+        if ($this->isRoot($path)) {
350
+            return 'dir';
351
+        }
352
+
353
+        try {
354
+            if ($this->headObject($path)) {
355
+                return 'file';
356
+            }
357
+            if ($this->headObject($path . '/')) {
358
+                return 'dir';
359
+            }
360
+        } catch (S3Exception $e) {
361
+            \OCP\Util::logException('files_external', $e);
362
+            return false;
363
+        }
364
+
365
+        return false;
366
+    }
367
+
368
+    public function getPermissions($path) {
369
+        $type = $this->filetype($path);
370
+        if (!$type) {
371
+            return 0;
372
+        }
373
+        return $type === 'dir' ? Constants::PERMISSION_ALL : Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
374
+    }
375
+
376
+    public function unlink($path) {
377
+        $path = $this->normalizePath($path);
378
+
379
+        if ($this->is_dir($path)) {
380
+            return $this->rmdir($path);
381
+        }
382
+
383
+        try {
384
+            $this->deleteObject($path);
385
+            $this->invalidateCache($path);
386
+        } catch (S3Exception $e) {
387
+            \OCP\Util::logException('files_external', $e);
388
+            return false;
389
+        }
390
+
391
+        return true;
392
+    }
393
+
394
+    public function fopen($path, $mode) {
395
+        $path = $this->normalizePath($path);
396
+
397
+        switch ($mode) {
398
+            case 'r':
399
+            case 'rb':
400
+                try {
401
+                    return $this->readObject($path);
402
+                } catch (S3Exception $e) {
403
+                    \OCP\Util::logException('files_external', $e);
404
+                    return false;
405
+                }
406
+            case 'w':
407
+            case 'wb':
408
+                $tmpFile = \OCP\Files::tmpFile();
409
+
410
+                $handle = fopen($tmpFile, 'w');
411
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
412
+                    $this->writeBack($tmpFile, $path);
413
+                });
414
+            case 'a':
415
+            case 'ab':
416
+            case 'r+':
417
+            case 'w+':
418
+            case 'wb+':
419
+            case 'a+':
420
+            case 'x':
421
+            case 'x+':
422
+            case 'c':
423
+            case 'c+':
424
+                if (strrpos($path, '.') !== false) {
425
+                    $ext = substr($path, strrpos($path, '.'));
426
+                } else {
427
+                    $ext = '';
428
+                }
429
+                $tmpFile = \OCP\Files::tmpFile($ext);
430
+                if ($this->file_exists($path)) {
431
+                    $source = $this->readObject($path);
432
+                    file_put_contents($tmpFile, $source);
433
+                }
434
+
435
+                $handle = fopen($tmpFile, $mode);
436
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
437
+                    $this->writeBack($tmpFile, $path);
438
+                });
439
+        }
440
+        return false;
441
+    }
442
+
443
+    public function touch($path, $mtime = null) {
444
+        $path = $this->normalizePath($path);
445
+
446
+        $metadata = array();
447
+        if (is_null($mtime)) {
448
+            $mtime = time();
449
+        }
450
+        $metadata = [
451
+            'lastmodified' => gmdate(\DateTime::RFC1123, $mtime)
452
+        ];
453
+
454
+        $fileType = $this->filetype($path);
455
+        try {
456
+            if ($fileType !== false) {
457
+                if ($fileType === 'dir' && !$this->isRoot($path)) {
458
+                    $path .= '/';
459
+                }
460
+                $this->getConnection()->copyObject([
461
+                    'Bucket' => $this->bucket,
462
+                    'Key' => $this->cleanKey($path),
463
+                    'Metadata' => $metadata,
464
+                    'CopySource' => $this->bucket . '/' . $path,
465
+                    'MetadataDirective' => 'REPLACE',
466
+                ]);
467
+                $this->testTimeout();
468
+            } else {
469
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
470
+                $this->getConnection()->putObject([
471
+                    'Bucket' => $this->bucket,
472
+                    'Key' => $this->cleanKey($path),
473
+                    'Metadata' => $metadata,
474
+                    'Body' => '',
475
+                    'ContentType' => $mimeType,
476
+                    'MetadataDirective' => 'REPLACE',
477
+                ]);
478
+                $this->testTimeout();
479
+            }
480
+        } catch (S3Exception $e) {
481
+            \OCP\Util::logException('files_external', $e);
482
+            return false;
483
+        }
484
+
485
+        $this->invalidateCache($path);
486
+        return true;
487
+    }
488
+
489
+    public function copy($path1, $path2) {
490
+        $path1 = $this->normalizePath($path1);
491
+        $path2 = $this->normalizePath($path2);
492
+
493
+        if ($this->is_file($path1)) {
494
+            try {
495
+                $this->getConnection()->copyObject(array(
496
+                    'Bucket' => $this->bucket,
497
+                    'Key' => $this->cleanKey($path2),
498
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
499
+                ));
500
+                $this->testTimeout();
501
+            } catch (S3Exception $e) {
502
+                \OCP\Util::logException('files_external', $e);
503
+                return false;
504
+            }
505
+        } else {
506
+            $this->remove($path2);
507
+
508
+            try {
509
+                $this->getConnection()->copyObject(array(
510
+                    'Bucket' => $this->bucket,
511
+                    'Key' => $path2 . '/',
512
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
513
+                ));
514
+                $this->testTimeout();
515
+            } catch (S3Exception $e) {
516
+                \OCP\Util::logException('files_external', $e);
517
+                return false;
518
+            }
519
+
520
+            $dh = $this->opendir($path1);
521
+            if (is_resource($dh)) {
522
+                while (($file = readdir($dh)) !== false) {
523
+                    if (\OC\Files\Filesystem::isIgnoredDir($file)) {
524
+                        continue;
525
+                    }
526
+
527
+                    $source = $path1 . '/' . $file;
528
+                    $target = $path2 . '/' . $file;
529
+                    $this->copy($source, $target);
530
+                }
531
+            }
532
+        }
533
+
534
+        $this->invalidateCache($path2);
535
+
536
+        return true;
537
+    }
538
+
539
+    public function rename($path1, $path2) {
540
+        $path1 = $this->normalizePath($path1);
541
+        $path2 = $this->normalizePath($path2);
542
+
543
+        if ($this->is_file($path1)) {
544
+
545
+            if ($this->copy($path1, $path2) === false) {
546
+                return false;
547
+            }
548
+
549
+            if ($this->unlink($path1) === false) {
550
+                $this->unlink($path2);
551
+                return false;
552
+            }
553
+        } else {
554
+
555
+            if ($this->copy($path1, $path2) === false) {
556
+                return false;
557
+            }
558
+
559
+            if ($this->rmdir($path1) === false) {
560
+                $this->rmdir($path2);
561
+                return false;
562
+            }
563
+        }
564
+
565
+        return true;
566
+    }
567
+
568
+    public function test() {
569
+        $test = $this->getConnection()->getBucketAcl(array(
570
+            'Bucket' => $this->bucket,
571
+        ));
572
+        if (isset($test) && !is_null($test->getPath('Owner/ID'))) {
573
+            return true;
574
+        }
575
+        return false;
576
+    }
577
+
578
+    public function getId() {
579
+        return $this->id;
580
+    }
581
+
582
+    public function writeBack($tmpFile, $path) {
583
+        try {
584
+            $source = fopen($tmpFile, 'r');
585
+            $this->writeObject($path, $source);
586
+            $this->invalidateCache($path);
587
+            fclose($source);
588
+
589
+            unlink($tmpFile);
590
+            return true;
591
+        } catch (S3Exception $e) {
592
+            \OCP\Util::logException('files_external', $e);
593
+            return false;
594
+        }
595
+    }
596
+
597
+    /**
598
+     * check if curl is installed
599
+     */
600
+    public static function checkDependencies() {
601
+        return true;
602
+    }
603 603
 
604 604
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Cache.php 2 patches
Indentation   +828 added lines, -828 removed lines patch added patch discarded remove patch
@@ -56,842 +56,842 @@
 block discarded – undo
56 56
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
57 57
  */
58 58
 class Cache implements ICache {
59
-	use MoveFromCacheTrait {
60
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
61
-	}
62
-
63
-	/**
64
-	 * @var array partial data for the cache
65
-	 */
66
-	protected $partial = array();
67
-
68
-	/**
69
-	 * @var string
70
-	 */
71
-	protected $storageId;
72
-
73
-	/**
74
-	 * @var Storage $storageCache
75
-	 */
76
-	protected $storageCache;
77
-
78
-	/** @var IMimeTypeLoader */
79
-	protected $mimetypeLoader;
80
-
81
-	/**
82
-	 * @var IDBConnection
83
-	 */
84
-	protected $connection;
85
-
86
-	/** @var QuerySearchHelper */
87
-	protected $querySearchHelper;
88
-
89
-	/**
90
-	 * @param \OC\Files\Storage\Storage|string $storage
91
-	 */
92
-	public function __construct($storage) {
93
-		if ($storage instanceof \OC\Files\Storage\Storage) {
94
-			$this->storageId = $storage->getId();
95
-		} else {
96
-			$this->storageId = $storage;
97
-		}
98
-		if (strlen($this->storageId) > 64) {
99
-			$this->storageId = md5($this->storageId);
100
-		}
101
-
102
-		$this->storageCache = new Storage($storage);
103
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
104
-		$this->connection = \OC::$server->getDatabaseConnection();
105
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
106
-	}
107
-
108
-	/**
109
-	 * Get the numeric storage id for this cache's storage
110
-	 *
111
-	 * @return int
112
-	 */
113
-	public function getNumericStorageId() {
114
-		return $this->storageCache->getNumericId();
115
-	}
116
-
117
-	/**
118
-	 * get the stored metadata of a file or folder
119
-	 *
120
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
121
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
122
-	 */
123
-	public function get($file) {
124
-		if (is_string($file) or $file == '') {
125
-			// normalize file
126
-			$file = $this->normalize($file);
127
-
128
-			$where = 'WHERE `storage` = ? AND `path_hash` = ?';
129
-			$params = array($this->getNumericStorageId(), md5($file));
130
-		} else { //file id
131
-			$where = 'WHERE `fileid` = ?';
132
-			$params = array($file);
133
-		}
134
-		$sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
59
+    use MoveFromCacheTrait {
60
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
61
+    }
62
+
63
+    /**
64
+     * @var array partial data for the cache
65
+     */
66
+    protected $partial = array();
67
+
68
+    /**
69
+     * @var string
70
+     */
71
+    protected $storageId;
72
+
73
+    /**
74
+     * @var Storage $storageCache
75
+     */
76
+    protected $storageCache;
77
+
78
+    /** @var IMimeTypeLoader */
79
+    protected $mimetypeLoader;
80
+
81
+    /**
82
+     * @var IDBConnection
83
+     */
84
+    protected $connection;
85
+
86
+    /** @var QuerySearchHelper */
87
+    protected $querySearchHelper;
88
+
89
+    /**
90
+     * @param \OC\Files\Storage\Storage|string $storage
91
+     */
92
+    public function __construct($storage) {
93
+        if ($storage instanceof \OC\Files\Storage\Storage) {
94
+            $this->storageId = $storage->getId();
95
+        } else {
96
+            $this->storageId = $storage;
97
+        }
98
+        if (strlen($this->storageId) > 64) {
99
+            $this->storageId = md5($this->storageId);
100
+        }
101
+
102
+        $this->storageCache = new Storage($storage);
103
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
104
+        $this->connection = \OC::$server->getDatabaseConnection();
105
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
106
+    }
107
+
108
+    /**
109
+     * Get the numeric storage id for this cache's storage
110
+     *
111
+     * @return int
112
+     */
113
+    public function getNumericStorageId() {
114
+        return $this->storageCache->getNumericId();
115
+    }
116
+
117
+    /**
118
+     * get the stored metadata of a file or folder
119
+     *
120
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
121
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
122
+     */
123
+    public function get($file) {
124
+        if (is_string($file) or $file == '') {
125
+            // normalize file
126
+            $file = $this->normalize($file);
127
+
128
+            $where = 'WHERE `storage` = ? AND `path_hash` = ?';
129
+            $params = array($this->getNumericStorageId(), md5($file));
130
+        } else { //file id
131
+            $where = 'WHERE `fileid` = ?';
132
+            $params = array($file);
133
+        }
134
+        $sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
135 135
 					   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
136 136
 				FROM `*PREFIX*filecache` ' . $where;
137
-		$result = $this->connection->executeQuery($sql, $params);
138
-		$data = $result->fetch();
139
-
140
-		//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
141
-		//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
142
-		if ($data === null) {
143
-			$data = false;
144
-		}
145
-
146
-		//merge partial data
147
-		if (!$data and is_string($file)) {
148
-			if (isset($this->partial[$file])) {
149
-				$data = $this->partial[$file];
150
-			}
151
-			return $data;
152
-		} else {
153
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * Create a CacheEntry from database row
159
-	 *
160
-	 * @param array $data
161
-	 * @param IMimeTypeLoader $mimetypeLoader
162
-	 * @return CacheEntry
163
-	 */
164
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
165
-		//fix types
166
-		$data['fileid'] = (int)$data['fileid'];
167
-		$data['parent'] = (int)$data['parent'];
168
-		$data['size'] = 0 + $data['size'];
169
-		$data['mtime'] = (int)$data['mtime'];
170
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
171
-		$data['encryptedVersion'] = (int)$data['encrypted'];
172
-		$data['encrypted'] = (bool)$data['encrypted'];
173
-		$data['storage_id'] = $data['storage'];
174
-		$data['storage'] = (int)$data['storage'];
175
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
176
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
177
-		if ($data['storage_mtime'] == 0) {
178
-			$data['storage_mtime'] = $data['mtime'];
179
-		}
180
-		$data['permissions'] = (int)$data['permissions'];
181
-		return new CacheEntry($data);
182
-	}
183
-
184
-	/**
185
-	 * get the metadata of all files stored in $folder
186
-	 *
187
-	 * @param string $folder
188
-	 * @return ICacheEntry[]
189
-	 */
190
-	public function getFolderContents($folder) {
191
-		$fileId = $this->getId($folder);
192
-		return $this->getFolderContentsById($fileId);
193
-	}
194
-
195
-	/**
196
-	 * get the metadata of all files stored in $folder
197
-	 *
198
-	 * @param int $fileId the file id of the folder
199
-	 * @return ICacheEntry[]
200
-	 */
201
-	public function getFolderContentsById($fileId) {
202
-		if ($fileId > -1) {
203
-			$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
137
+        $result = $this->connection->executeQuery($sql, $params);
138
+        $data = $result->fetch();
139
+
140
+        //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
141
+        //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
142
+        if ($data === null) {
143
+            $data = false;
144
+        }
145
+
146
+        //merge partial data
147
+        if (!$data and is_string($file)) {
148
+            if (isset($this->partial[$file])) {
149
+                $data = $this->partial[$file];
150
+            }
151
+            return $data;
152
+        } else {
153
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
154
+        }
155
+    }
156
+
157
+    /**
158
+     * Create a CacheEntry from database row
159
+     *
160
+     * @param array $data
161
+     * @param IMimeTypeLoader $mimetypeLoader
162
+     * @return CacheEntry
163
+     */
164
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
165
+        //fix types
166
+        $data['fileid'] = (int)$data['fileid'];
167
+        $data['parent'] = (int)$data['parent'];
168
+        $data['size'] = 0 + $data['size'];
169
+        $data['mtime'] = (int)$data['mtime'];
170
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
171
+        $data['encryptedVersion'] = (int)$data['encrypted'];
172
+        $data['encrypted'] = (bool)$data['encrypted'];
173
+        $data['storage_id'] = $data['storage'];
174
+        $data['storage'] = (int)$data['storage'];
175
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
176
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
177
+        if ($data['storage_mtime'] == 0) {
178
+            $data['storage_mtime'] = $data['mtime'];
179
+        }
180
+        $data['permissions'] = (int)$data['permissions'];
181
+        return new CacheEntry($data);
182
+    }
183
+
184
+    /**
185
+     * get the metadata of all files stored in $folder
186
+     *
187
+     * @param string $folder
188
+     * @return ICacheEntry[]
189
+     */
190
+    public function getFolderContents($folder) {
191
+        $fileId = $this->getId($folder);
192
+        return $this->getFolderContentsById($fileId);
193
+    }
194
+
195
+    /**
196
+     * get the metadata of all files stored in $folder
197
+     *
198
+     * @param int $fileId the file id of the folder
199
+     * @return ICacheEntry[]
200
+     */
201
+    public function getFolderContentsById($fileId) {
202
+        if ($fileId > -1) {
203
+            $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
204 204
 						   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
205 205
 					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
206
-			$result = $this->connection->executeQuery($sql, [$fileId]);
207
-			$files = $result->fetchAll();
208
-			return array_map(function (array $data) {
209
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);;
210
-			}, $files);
211
-		}
212
-		return [];
213
-	}
214
-
215
-	/**
216
-	 * insert or update meta data for a file or folder
217
-	 *
218
-	 * @param string $file
219
-	 * @param array $data
220
-	 *
221
-	 * @return int file id
222
-	 * @throws \RuntimeException
223
-	 */
224
-	public function put($file, array $data) {
225
-		if (($id = $this->getId($file)) > -1) {
226
-			$this->update($id, $data);
227
-			return $id;
228
-		} else {
229
-			return $this->insert($file, $data);
230
-		}
231
-	}
232
-
233
-	/**
234
-	 * insert meta data for a new file or folder
235
-	 *
236
-	 * @param string $file
237
-	 * @param array $data
238
-	 *
239
-	 * @return int file id
240
-	 * @throws \RuntimeException
241
-	 */
242
-	public function insert($file, array $data) {
243
-		// normalize file
244
-		$file = $this->normalize($file);
245
-
246
-		if (isset($this->partial[$file])) { //add any saved partial data
247
-			$data = array_merge($this->partial[$file], $data);
248
-			unset($this->partial[$file]);
249
-		}
250
-
251
-		$requiredFields = array('size', 'mtime', 'mimetype');
252
-		foreach ($requiredFields as $field) {
253
-			if (!isset($data[$field])) { //data not complete save as partial and return
254
-				$this->partial[$file] = $data;
255
-				return -1;
256
-			}
257
-		}
258
-
259
-		$data['path'] = $file;
260
-		$data['parent'] = $this->getParentId($file);
261
-		$data['name'] = basename($file);
262
-
263
-		list($queryParts, $params) = $this->buildParts($data);
264
-		$queryParts[] = '`storage`';
265
-		$params[] = $this->getNumericStorageId();
266
-
267
-		$queryParts = array_map(function ($item) {
268
-			return trim($item, "`");
269
-		}, $queryParts);
270
-		$values = array_combine($queryParts, $params);
271
-		if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
272
-			'storage',
273
-			'path_hash',
274
-		])
275
-		) {
276
-			return (int)$this->connection->lastInsertId('*PREFIX*filecache');
277
-		}
278
-
279
-		// The file was created in the mean time
280
-		if (($id = $this->getId($file)) > -1) {
281
-			$this->update($id, $data);
282
-			return $id;
283
-		} else {
284
-			throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
285
-		}
286
-	}
287
-
288
-	/**
289
-	 * update the metadata of an existing file or folder in the cache
290
-	 *
291
-	 * @param int $id the fileid of the existing file or folder
292
-	 * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
293
-	 */
294
-	public function update($id, array $data) {
295
-
296
-		if (isset($data['path'])) {
297
-			// normalize path
298
-			$data['path'] = $this->normalize($data['path']);
299
-		}
300
-
301
-		if (isset($data['name'])) {
302
-			// normalize path
303
-			$data['name'] = $this->normalize($data['name']);
304
-		}
305
-
306
-		list($queryParts, $params) = $this->buildParts($data);
307
-		// duplicate $params because we need the parts twice in the SQL statement
308
-		// once for the SET part, once in the WHERE clause
309
-		$params = array_merge($params, $params);
310
-		$params[] = $id;
311
-
312
-		// don't update if the data we try to set is the same as the one in the record
313
-		// some databases (Postgres) don't like superfluous updates
314
-		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
315
-			'WHERE (' .
316
-			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
317
-			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
318
-			') AND `fileid` = ? ';
319
-		$this->connection->executeQuery($sql, $params);
320
-
321
-	}
322
-
323
-	/**
324
-	 * extract query parts and params array from data array
325
-	 *
326
-	 * @param array $data
327
-	 * @return array [$queryParts, $params]
328
-	 *        $queryParts: string[], the (escaped) column names to be set in the query
329
-	 *        $params: mixed[], the new values for the columns, to be passed as params to the query
330
-	 */
331
-	protected function buildParts(array $data) {
332
-		$fields = array(
333
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
334
-			'etag', 'permissions', 'checksum', 'storage');
335
-
336
-		$doNotCopyStorageMTime = false;
337
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
338
-			// this horrific magic tells it to not copy storage_mtime to mtime
339
-			unset($data['mtime']);
340
-			$doNotCopyStorageMTime = true;
341
-		}
342
-
343
-		$params = array();
344
-		$queryParts = array();
345
-		foreach ($data as $name => $value) {
346
-			if (array_search($name, $fields) !== false) {
347
-				if ($name === 'path') {
348
-					$params[] = md5($value);
349
-					$queryParts[] = '`path_hash`';
350
-				} elseif ($name === 'mimetype') {
351
-					$params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
352
-					$queryParts[] = '`mimepart`';
353
-					$value = $this->mimetypeLoader->getId($value);
354
-				} elseif ($name === 'storage_mtime') {
355
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
356
-						$params[] = $value;
357
-						$queryParts[] = '`mtime`';
358
-					}
359
-				} elseif ($name === 'encrypted') {
360
-					if (isset($data['encryptedVersion'])) {
361
-						$value = $data['encryptedVersion'];
362
-					} else {
363
-						// Boolean to integer conversion
364
-						$value = $value ? 1 : 0;
365
-					}
366
-				}
367
-				$params[] = $value;
368
-				$queryParts[] = '`' . $name . '`';
369
-			}
370
-		}
371
-		return array($queryParts, $params);
372
-	}
373
-
374
-	/**
375
-	 * get the file id for a file
376
-	 *
377
-	 * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
378
-	 *
379
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
380
-	 *
381
-	 * @param string $file
382
-	 * @return int
383
-	 */
384
-	public function getId($file) {
385
-		// normalize file
386
-		$file = $this->normalize($file);
387
-
388
-		$pathHash = md5($file);
389
-
390
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
391
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
392
-		if ($row = $result->fetch()) {
393
-			return $row['fileid'];
394
-		} else {
395
-			return -1;
396
-		}
397
-	}
398
-
399
-	/**
400
-	 * get the id of the parent folder of a file
401
-	 *
402
-	 * @param string $file
403
-	 * @return int
404
-	 */
405
-	public function getParentId($file) {
406
-		if ($file === '') {
407
-			return -1;
408
-		} else {
409
-			$parent = $this->getParentPath($file);
410
-			return (int)$this->getId($parent);
411
-		}
412
-	}
413
-
414
-	private function getParentPath($path) {
415
-		$parent = dirname($path);
416
-		if ($parent === '.') {
417
-			$parent = '';
418
-		}
419
-		return $parent;
420
-	}
421
-
422
-	/**
423
-	 * check if a file is available in the cache
424
-	 *
425
-	 * @param string $file
426
-	 * @return bool
427
-	 */
428
-	public function inCache($file) {
429
-		return $this->getId($file) != -1;
430
-	}
431
-
432
-	/**
433
-	 * remove a file or folder from the cache
434
-	 *
435
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
436
-	 *
437
-	 * @param string $file
438
-	 */
439
-	public function remove($file) {
440
-		$entry = $this->get($file);
441
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
442
-		$this->connection->executeQuery($sql, array($entry['fileid']));
443
-		if ($entry['mimetype'] === 'httpd/unix-directory') {
444
-			$this->removeChildren($entry);
445
-		}
446
-	}
447
-
448
-	/**
449
-	 * Get all sub folders of a folder
450
-	 *
451
-	 * @param array $entry the cache entry of the folder to get the subfolders for
452
-	 * @return array[] the cache entries for the subfolders
453
-	 */
454
-	private function getSubFolders($entry) {
455
-		$children = $this->getFolderContentsById($entry['fileid']);
456
-		return array_filter($children, function ($child) {
457
-			return $child['mimetype'] === 'httpd/unix-directory';
458
-		});
459
-	}
460
-
461
-	/**
462
-	 * Recursively remove all children of a folder
463
-	 *
464
-	 * @param array $entry the cache entry of the folder to remove the children of
465
-	 * @throws \OC\DatabaseException
466
-	 */
467
-	private function removeChildren($entry) {
468
-		$subFolders = $this->getSubFolders($entry);
469
-		foreach ($subFolders as $folder) {
470
-			$this->removeChildren($folder);
471
-		}
472
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
473
-		$this->connection->executeQuery($sql, array($entry['fileid']));
474
-	}
475
-
476
-	/**
477
-	 * Move a file or folder in the cache
478
-	 *
479
-	 * @param string $source
480
-	 * @param string $target
481
-	 */
482
-	public function move($source, $target) {
483
-		$this->moveFromCache($this, $source, $target);
484
-	}
485
-
486
-	/**
487
-	 * Get the storage id and path needed for a move
488
-	 *
489
-	 * @param string $path
490
-	 * @return array [$storageId, $internalPath]
491
-	 */
492
-	protected function getMoveInfo($path) {
493
-		return [$this->getNumericStorageId(), $path];
494
-	}
495
-
496
-	/**
497
-	 * Move a file or folder in the cache
498
-	 *
499
-	 * @param \OCP\Files\Cache\ICache $sourceCache
500
-	 * @param string $sourcePath
501
-	 * @param string $targetPath
502
-	 * @throws \OC\DatabaseException
503
-	 * @throws \Exception if the given storages have an invalid id
504
-	 * @suppress SqlInjectionChecker
505
-	 */
506
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
507
-		if ($sourceCache instanceof Cache) {
508
-			// normalize source and target
509
-			$sourcePath = $this->normalize($sourcePath);
510
-			$targetPath = $this->normalize($targetPath);
511
-
512
-			$sourceData = $sourceCache->get($sourcePath);
513
-			$sourceId = $sourceData['fileid'];
514
-			$newParentId = $this->getParentId($targetPath);
515
-
516
-			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
517
-			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
518
-
519
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
520
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
521
-			}
522
-			if (is_null($targetStorageId) || $targetStorageId === false) {
523
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
524
-			}
525
-
526
-			$this->connection->beginTransaction();
527
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
528
-				//update all child entries
529
-				$sourceLength = mb_strlen($sourcePath);
530
-				$query = $this->connection->getQueryBuilder();
531
-
532
-				$fun = $query->func();
533
-				$newPathFunction = $fun->concat(
534
-					$query->createNamedParameter($targetPath),
535
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
536
-				);
537
-				$query->update('filecache')
538
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
539
-					->set('path_hash', $fun->md5($newPathFunction))
540
-					->set('path', $newPathFunction)
541
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
542
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
543
-
544
-				try {
545
-					$query->execute();
546
-				} catch (\OC\DatabaseException $e) {
547
-					$this->connection->rollBack();
548
-					throw $e;
549
-				}
550
-			}
551
-
552
-			$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
553
-			$this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId));
554
-			$this->connection->commit();
555
-		} else {
556
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
557
-		}
558
-	}
559
-
560
-	/**
561
-	 * remove all entries for files that are stored on the storage from the cache
562
-	 */
563
-	public function clear() {
564
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
565
-		$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
566
-
567
-		$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
568
-		$this->connection->executeQuery($sql, array($this->storageId));
569
-	}
570
-
571
-	/**
572
-	 * Get the scan status of a file
573
-	 *
574
-	 * - Cache::NOT_FOUND: File is not in the cache
575
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
576
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
577
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
578
-	 *
579
-	 * @param string $file
580
-	 *
581
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
582
-	 */
583
-	public function getStatus($file) {
584
-		// normalize file
585
-		$file = $this->normalize($file);
586
-
587
-		$pathHash = md5($file);
588
-		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
589
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
590
-		if ($row = $result->fetch()) {
591
-			if ((int)$row['size'] === -1) {
592
-				return self::SHALLOW;
593
-			} else {
594
-				return self::COMPLETE;
595
-			}
596
-		} else {
597
-			if (isset($this->partial[$file])) {
598
-				return self::PARTIAL;
599
-			} else {
600
-				return self::NOT_FOUND;
601
-			}
602
-		}
603
-	}
604
-
605
-	/**
606
-	 * search for files matching $pattern
607
-	 *
608
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
609
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
610
-	 */
611
-	public function search($pattern) {
612
-		// normalize pattern
613
-		$pattern = $this->normalize($pattern);
614
-
615
-		if ($pattern === '%%') {
616
-			return [];
617
-		}
618
-
619
-
620
-		$sql = '
206
+            $result = $this->connection->executeQuery($sql, [$fileId]);
207
+            $files = $result->fetchAll();
208
+            return array_map(function (array $data) {
209
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);;
210
+            }, $files);
211
+        }
212
+        return [];
213
+    }
214
+
215
+    /**
216
+     * insert or update meta data for a file or folder
217
+     *
218
+     * @param string $file
219
+     * @param array $data
220
+     *
221
+     * @return int file id
222
+     * @throws \RuntimeException
223
+     */
224
+    public function put($file, array $data) {
225
+        if (($id = $this->getId($file)) > -1) {
226
+            $this->update($id, $data);
227
+            return $id;
228
+        } else {
229
+            return $this->insert($file, $data);
230
+        }
231
+    }
232
+
233
+    /**
234
+     * insert meta data for a new file or folder
235
+     *
236
+     * @param string $file
237
+     * @param array $data
238
+     *
239
+     * @return int file id
240
+     * @throws \RuntimeException
241
+     */
242
+    public function insert($file, array $data) {
243
+        // normalize file
244
+        $file = $this->normalize($file);
245
+
246
+        if (isset($this->partial[$file])) { //add any saved partial data
247
+            $data = array_merge($this->partial[$file], $data);
248
+            unset($this->partial[$file]);
249
+        }
250
+
251
+        $requiredFields = array('size', 'mtime', 'mimetype');
252
+        foreach ($requiredFields as $field) {
253
+            if (!isset($data[$field])) { //data not complete save as partial and return
254
+                $this->partial[$file] = $data;
255
+                return -1;
256
+            }
257
+        }
258
+
259
+        $data['path'] = $file;
260
+        $data['parent'] = $this->getParentId($file);
261
+        $data['name'] = basename($file);
262
+
263
+        list($queryParts, $params) = $this->buildParts($data);
264
+        $queryParts[] = '`storage`';
265
+        $params[] = $this->getNumericStorageId();
266
+
267
+        $queryParts = array_map(function ($item) {
268
+            return trim($item, "`");
269
+        }, $queryParts);
270
+        $values = array_combine($queryParts, $params);
271
+        if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
272
+            'storage',
273
+            'path_hash',
274
+        ])
275
+        ) {
276
+            return (int)$this->connection->lastInsertId('*PREFIX*filecache');
277
+        }
278
+
279
+        // The file was created in the mean time
280
+        if (($id = $this->getId($file)) > -1) {
281
+            $this->update($id, $data);
282
+            return $id;
283
+        } else {
284
+            throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
285
+        }
286
+    }
287
+
288
+    /**
289
+     * update the metadata of an existing file or folder in the cache
290
+     *
291
+     * @param int $id the fileid of the existing file or folder
292
+     * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
293
+     */
294
+    public function update($id, array $data) {
295
+
296
+        if (isset($data['path'])) {
297
+            // normalize path
298
+            $data['path'] = $this->normalize($data['path']);
299
+        }
300
+
301
+        if (isset($data['name'])) {
302
+            // normalize path
303
+            $data['name'] = $this->normalize($data['name']);
304
+        }
305
+
306
+        list($queryParts, $params) = $this->buildParts($data);
307
+        // duplicate $params because we need the parts twice in the SQL statement
308
+        // once for the SET part, once in the WHERE clause
309
+        $params = array_merge($params, $params);
310
+        $params[] = $id;
311
+
312
+        // don't update if the data we try to set is the same as the one in the record
313
+        // some databases (Postgres) don't like superfluous updates
314
+        $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
315
+            'WHERE (' .
316
+            implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
317
+            implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
318
+            ') AND `fileid` = ? ';
319
+        $this->connection->executeQuery($sql, $params);
320
+
321
+    }
322
+
323
+    /**
324
+     * extract query parts and params array from data array
325
+     *
326
+     * @param array $data
327
+     * @return array [$queryParts, $params]
328
+     *        $queryParts: string[], the (escaped) column names to be set in the query
329
+     *        $params: mixed[], the new values for the columns, to be passed as params to the query
330
+     */
331
+    protected function buildParts(array $data) {
332
+        $fields = array(
333
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
334
+            'etag', 'permissions', 'checksum', 'storage');
335
+
336
+        $doNotCopyStorageMTime = false;
337
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
338
+            // this horrific magic tells it to not copy storage_mtime to mtime
339
+            unset($data['mtime']);
340
+            $doNotCopyStorageMTime = true;
341
+        }
342
+
343
+        $params = array();
344
+        $queryParts = array();
345
+        foreach ($data as $name => $value) {
346
+            if (array_search($name, $fields) !== false) {
347
+                if ($name === 'path') {
348
+                    $params[] = md5($value);
349
+                    $queryParts[] = '`path_hash`';
350
+                } elseif ($name === 'mimetype') {
351
+                    $params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
352
+                    $queryParts[] = '`mimepart`';
353
+                    $value = $this->mimetypeLoader->getId($value);
354
+                } elseif ($name === 'storage_mtime') {
355
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
356
+                        $params[] = $value;
357
+                        $queryParts[] = '`mtime`';
358
+                    }
359
+                } elseif ($name === 'encrypted') {
360
+                    if (isset($data['encryptedVersion'])) {
361
+                        $value = $data['encryptedVersion'];
362
+                    } else {
363
+                        // Boolean to integer conversion
364
+                        $value = $value ? 1 : 0;
365
+                    }
366
+                }
367
+                $params[] = $value;
368
+                $queryParts[] = '`' . $name . '`';
369
+            }
370
+        }
371
+        return array($queryParts, $params);
372
+    }
373
+
374
+    /**
375
+     * get the file id for a file
376
+     *
377
+     * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
378
+     *
379
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
380
+     *
381
+     * @param string $file
382
+     * @return int
383
+     */
384
+    public function getId($file) {
385
+        // normalize file
386
+        $file = $this->normalize($file);
387
+
388
+        $pathHash = md5($file);
389
+
390
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
391
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
392
+        if ($row = $result->fetch()) {
393
+            return $row['fileid'];
394
+        } else {
395
+            return -1;
396
+        }
397
+    }
398
+
399
+    /**
400
+     * get the id of the parent folder of a file
401
+     *
402
+     * @param string $file
403
+     * @return int
404
+     */
405
+    public function getParentId($file) {
406
+        if ($file === '') {
407
+            return -1;
408
+        } else {
409
+            $parent = $this->getParentPath($file);
410
+            return (int)$this->getId($parent);
411
+        }
412
+    }
413
+
414
+    private function getParentPath($path) {
415
+        $parent = dirname($path);
416
+        if ($parent === '.') {
417
+            $parent = '';
418
+        }
419
+        return $parent;
420
+    }
421
+
422
+    /**
423
+     * check if a file is available in the cache
424
+     *
425
+     * @param string $file
426
+     * @return bool
427
+     */
428
+    public function inCache($file) {
429
+        return $this->getId($file) != -1;
430
+    }
431
+
432
+    /**
433
+     * remove a file or folder from the cache
434
+     *
435
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
436
+     *
437
+     * @param string $file
438
+     */
439
+    public function remove($file) {
440
+        $entry = $this->get($file);
441
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
442
+        $this->connection->executeQuery($sql, array($entry['fileid']));
443
+        if ($entry['mimetype'] === 'httpd/unix-directory') {
444
+            $this->removeChildren($entry);
445
+        }
446
+    }
447
+
448
+    /**
449
+     * Get all sub folders of a folder
450
+     *
451
+     * @param array $entry the cache entry of the folder to get the subfolders for
452
+     * @return array[] the cache entries for the subfolders
453
+     */
454
+    private function getSubFolders($entry) {
455
+        $children = $this->getFolderContentsById($entry['fileid']);
456
+        return array_filter($children, function ($child) {
457
+            return $child['mimetype'] === 'httpd/unix-directory';
458
+        });
459
+    }
460
+
461
+    /**
462
+     * Recursively remove all children of a folder
463
+     *
464
+     * @param array $entry the cache entry of the folder to remove the children of
465
+     * @throws \OC\DatabaseException
466
+     */
467
+    private function removeChildren($entry) {
468
+        $subFolders = $this->getSubFolders($entry);
469
+        foreach ($subFolders as $folder) {
470
+            $this->removeChildren($folder);
471
+        }
472
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
473
+        $this->connection->executeQuery($sql, array($entry['fileid']));
474
+    }
475
+
476
+    /**
477
+     * Move a file or folder in the cache
478
+     *
479
+     * @param string $source
480
+     * @param string $target
481
+     */
482
+    public function move($source, $target) {
483
+        $this->moveFromCache($this, $source, $target);
484
+    }
485
+
486
+    /**
487
+     * Get the storage id and path needed for a move
488
+     *
489
+     * @param string $path
490
+     * @return array [$storageId, $internalPath]
491
+     */
492
+    protected function getMoveInfo($path) {
493
+        return [$this->getNumericStorageId(), $path];
494
+    }
495
+
496
+    /**
497
+     * Move a file or folder in the cache
498
+     *
499
+     * @param \OCP\Files\Cache\ICache $sourceCache
500
+     * @param string $sourcePath
501
+     * @param string $targetPath
502
+     * @throws \OC\DatabaseException
503
+     * @throws \Exception if the given storages have an invalid id
504
+     * @suppress SqlInjectionChecker
505
+     */
506
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
507
+        if ($sourceCache instanceof Cache) {
508
+            // normalize source and target
509
+            $sourcePath = $this->normalize($sourcePath);
510
+            $targetPath = $this->normalize($targetPath);
511
+
512
+            $sourceData = $sourceCache->get($sourcePath);
513
+            $sourceId = $sourceData['fileid'];
514
+            $newParentId = $this->getParentId($targetPath);
515
+
516
+            list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
517
+            list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
518
+
519
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
520
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
521
+            }
522
+            if (is_null($targetStorageId) || $targetStorageId === false) {
523
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
524
+            }
525
+
526
+            $this->connection->beginTransaction();
527
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
528
+                //update all child entries
529
+                $sourceLength = mb_strlen($sourcePath);
530
+                $query = $this->connection->getQueryBuilder();
531
+
532
+                $fun = $query->func();
533
+                $newPathFunction = $fun->concat(
534
+                    $query->createNamedParameter($targetPath),
535
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
536
+                );
537
+                $query->update('filecache')
538
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
539
+                    ->set('path_hash', $fun->md5($newPathFunction))
540
+                    ->set('path', $newPathFunction)
541
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
542
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
543
+
544
+                try {
545
+                    $query->execute();
546
+                } catch (\OC\DatabaseException $e) {
547
+                    $this->connection->rollBack();
548
+                    throw $e;
549
+                }
550
+            }
551
+
552
+            $sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
553
+            $this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId));
554
+            $this->connection->commit();
555
+        } else {
556
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
557
+        }
558
+    }
559
+
560
+    /**
561
+     * remove all entries for files that are stored on the storage from the cache
562
+     */
563
+    public function clear() {
564
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
565
+        $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
566
+
567
+        $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
568
+        $this->connection->executeQuery($sql, array($this->storageId));
569
+    }
570
+
571
+    /**
572
+     * Get the scan status of a file
573
+     *
574
+     * - Cache::NOT_FOUND: File is not in the cache
575
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
576
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
577
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
578
+     *
579
+     * @param string $file
580
+     *
581
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
582
+     */
583
+    public function getStatus($file) {
584
+        // normalize file
585
+        $file = $this->normalize($file);
586
+
587
+        $pathHash = md5($file);
588
+        $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
589
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
590
+        if ($row = $result->fetch()) {
591
+            if ((int)$row['size'] === -1) {
592
+                return self::SHALLOW;
593
+            } else {
594
+                return self::COMPLETE;
595
+            }
596
+        } else {
597
+            if (isset($this->partial[$file])) {
598
+                return self::PARTIAL;
599
+            } else {
600
+                return self::NOT_FOUND;
601
+            }
602
+        }
603
+    }
604
+
605
+    /**
606
+     * search for files matching $pattern
607
+     *
608
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
609
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
610
+     */
611
+    public function search($pattern) {
612
+        // normalize pattern
613
+        $pattern = $this->normalize($pattern);
614
+
615
+        if ($pattern === '%%') {
616
+            return [];
617
+        }
618
+
619
+
620
+        $sql = '
621 621
 			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
622 622
 				`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
623 623
 				 `encrypted`, `etag`, `permissions`, `checksum`
624 624
 			FROM `*PREFIX*filecache`
625 625
 			WHERE `storage` = ? AND `name` ILIKE ?';
626
-		$result = $this->connection->executeQuery($sql,
627
-			[$this->getNumericStorageId(), $pattern]
628
-		);
629
-
630
-		return $this->searchResultToCacheEntries($result);
631
-	}
632
-
633
-	/**
634
-	 * @param Statement $result
635
-	 * @return CacheEntry[]
636
-	 */
637
-	private function searchResultToCacheEntries(Statement $result) {
638
-		$files = $result->fetchAll();
639
-
640
-		return array_map(function (array $data) {
641
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
642
-		}, $files);
643
-	}
644
-
645
-	/**
646
-	 * search for files by mimetype
647
-	 *
648
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
649
-	 *        where it will search for all mimetypes in the group ('image/*')
650
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
651
-	 */
652
-	public function searchByMime($mimetype) {
653
-		if (strpos($mimetype, '/')) {
654
-			$where = '`mimetype` = ?';
655
-		} else {
656
-			$where = '`mimepart` = ?';
657
-		}
658
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
626
+        $result = $this->connection->executeQuery($sql,
627
+            [$this->getNumericStorageId(), $pattern]
628
+        );
629
+
630
+        return $this->searchResultToCacheEntries($result);
631
+    }
632
+
633
+    /**
634
+     * @param Statement $result
635
+     * @return CacheEntry[]
636
+     */
637
+    private function searchResultToCacheEntries(Statement $result) {
638
+        $files = $result->fetchAll();
639
+
640
+        return array_map(function (array $data) {
641
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
642
+        }, $files);
643
+    }
644
+
645
+    /**
646
+     * search for files by mimetype
647
+     *
648
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
649
+     *        where it will search for all mimetypes in the group ('image/*')
650
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
651
+     */
652
+    public function searchByMime($mimetype) {
653
+        if (strpos($mimetype, '/')) {
654
+            $where = '`mimetype` = ?';
655
+        } else {
656
+            $where = '`mimepart` = ?';
657
+        }
658
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
659 659
 				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
660
-		$mimetype = $this->mimetypeLoader->getId($mimetype);
661
-		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
662
-
663
-		return $this->searchResultToCacheEntries($result);
664
-	}
665
-
666
-	public function searchQuery(ISearchQuery $searchQuery) {
667
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
668
-
669
-		$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
670
-			->from('filecache', 'file');
671
-
672
-		$query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
673
-
674
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
675
-			$query
676
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
677
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
678
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
679
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
680
-				))
681
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
682
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
683
-		}
684
-
685
-		$query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
686
-
687
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
688
-
689
-		if ($searchQuery->getLimit()) {
690
-			$query->setMaxResults($searchQuery->getLimit());
691
-		}
692
-		if ($searchQuery->getOffset()) {
693
-			$query->setFirstResult($searchQuery->getOffset());
694
-		}
695
-
696
-		$result = $query->execute();
697
-		return $this->searchResultToCacheEntries($result);
698
-	}
699
-
700
-	/**
701
-	 * Search for files by tag of a given users.
702
-	 *
703
-	 * Note that every user can tag files differently.
704
-	 *
705
-	 * @param string|int $tag name or tag id
706
-	 * @param string $userId owner of the tags
707
-	 * @return ICacheEntry[] file data
708
-	 */
709
-	public function searchByTag($tag, $userId) {
710
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
711
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
712
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
713
-			'FROM `*PREFIX*filecache` `file`, ' .
714
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
715
-			'`*PREFIX*vcategory` `tag` ' .
716
-			// JOIN filecache to vcategory_to_object
717
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
718
-			// JOIN vcategory_to_object to vcategory
719
-			'AND `tagmap`.`type` = `tag`.`type` ' .
720
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
721
-			// conditions
722
-			'AND `file`.`storage` = ? ' .
723
-			'AND `tag`.`type` = \'files\' ' .
724
-			'AND `tag`.`uid` = ? ';
725
-		if (is_int($tag)) {
726
-			$sql .= 'AND `tag`.`id` = ? ';
727
-		} else {
728
-			$sql .= 'AND `tag`.`category` = ? ';
729
-		}
730
-		$result = $this->connection->executeQuery(
731
-			$sql,
732
-			[
733
-				$this->getNumericStorageId(),
734
-				$userId,
735
-				$tag
736
-			]
737
-		);
738
-
739
-		$files = $result->fetchAll();
740
-
741
-		return array_map(function (array $data) {
742
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
743
-		}, $files);
744
-	}
745
-
746
-	/**
747
-	 * Re-calculate the folder size and the size of all parent folders
748
-	 *
749
-	 * @param string|boolean $path
750
-	 * @param array $data (optional) meta data of the folder
751
-	 */
752
-	public function correctFolderSize($path, $data = null) {
753
-		$this->calculateFolderSize($path, $data);
754
-		if ($path !== '') {
755
-			$parent = dirname($path);
756
-			if ($parent === '.' or $parent === '/') {
757
-				$parent = '';
758
-			}
759
-			$this->correctFolderSize($parent);
760
-		}
761
-	}
762
-
763
-	/**
764
-	 * calculate the size of a folder and set it in the cache
765
-	 *
766
-	 * @param string $path
767
-	 * @param array $entry (optional) meta data of the folder
768
-	 * @return int
769
-	 */
770
-	public function calculateFolderSize($path, $entry = null) {
771
-		$totalSize = 0;
772
-		if (is_null($entry) or !isset($entry['fileid'])) {
773
-			$entry = $this->get($path);
774
-		}
775
-		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
776
-			$id = $entry['fileid'];
777
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
778
-				'FROM `*PREFIX*filecache` ' .
779
-				'WHERE `parent` = ? AND `storage` = ?';
780
-			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
781
-			if ($row = $result->fetch()) {
782
-				$result->closeCursor();
783
-				list($sum, $min) = array_values($row);
784
-				$sum = 0 + $sum;
785
-				$min = 0 + $min;
786
-				if ($min === -1) {
787
-					$totalSize = $min;
788
-				} else {
789
-					$totalSize = $sum;
790
-				}
791
-				$update = array();
792
-				if ($entry['size'] !== $totalSize) {
793
-					$update['size'] = $totalSize;
794
-				}
795
-				if (count($update) > 0) {
796
-					$this->update($id, $update);
797
-				}
798
-			} else {
799
-				$result->closeCursor();
800
-			}
801
-		}
802
-		return $totalSize;
803
-	}
804
-
805
-	/**
806
-	 * get all file ids on the files on the storage
807
-	 *
808
-	 * @return int[]
809
-	 */
810
-	public function getAll() {
811
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
812
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
813
-		$ids = array();
814
-		while ($row = $result->fetch()) {
815
-			$ids[] = $row['fileid'];
816
-		}
817
-		return $ids;
818
-	}
819
-
820
-	/**
821
-	 * find a folder in the cache which has not been fully scanned
822
-	 *
823
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
824
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
825
-	 * likely the folder where we stopped scanning previously
826
-	 *
827
-	 * @return string|bool the path of the folder or false when no folder matched
828
-	 */
829
-	public function getIncomplete() {
830
-		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
831
-			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
832
-		$query->execute([$this->getNumericStorageId()]);
833
-		if ($row = $query->fetch()) {
834
-			return $row['path'];
835
-		} else {
836
-			return false;
837
-		}
838
-	}
839
-
840
-	/**
841
-	 * get the path of a file on this storage by it's file id
842
-	 *
843
-	 * @param int $id the file id of the file or folder to search
844
-	 * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
845
-	 */
846
-	public function getPathById($id) {
847
-		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
848
-		$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
849
-		if ($row = $result->fetch()) {
850
-			// Oracle stores empty strings as null...
851
-			if ($row['path'] === null) {
852
-				return '';
853
-			}
854
-			return $row['path'];
855
-		} else {
856
-			return null;
857
-		}
858
-	}
859
-
860
-	/**
861
-	 * get the storage id of the storage for a file and the internal path of the file
862
-	 * unlike getPathById this does not limit the search to files on this storage and
863
-	 * instead does a global search in the cache table
864
-	 *
865
-	 * @param int $id
866
-	 * @deprecated use getPathById() instead
867
-	 * @return array first element holding the storage id, second the path
868
-	 */
869
-	static public function getById($id) {
870
-		$connection = \OC::$server->getDatabaseConnection();
871
-		$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
872
-		$result = $connection->executeQuery($sql, array($id));
873
-		if ($row = $result->fetch()) {
874
-			$numericId = $row['storage'];
875
-			$path = $row['path'];
876
-		} else {
877
-			return null;
878
-		}
879
-
880
-		if ($id = Storage::getStorageId($numericId)) {
881
-			return array($id, $path);
882
-		} else {
883
-			return null;
884
-		}
885
-	}
886
-
887
-	/**
888
-	 * normalize the given path
889
-	 *
890
-	 * @param string $path
891
-	 * @return string
892
-	 */
893
-	public function normalize($path) {
894
-
895
-		return trim(\OC_Util::normalizeUnicode($path), '/');
896
-	}
660
+        $mimetype = $this->mimetypeLoader->getId($mimetype);
661
+        $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
662
+
663
+        return $this->searchResultToCacheEntries($result);
664
+    }
665
+
666
+    public function searchQuery(ISearchQuery $searchQuery) {
667
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
668
+
669
+        $query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
670
+            ->from('filecache', 'file');
671
+
672
+        $query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
673
+
674
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
675
+            $query
676
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
677
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
678
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
679
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
680
+                ))
681
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
682
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
683
+        }
684
+
685
+        $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
686
+
687
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
688
+
689
+        if ($searchQuery->getLimit()) {
690
+            $query->setMaxResults($searchQuery->getLimit());
691
+        }
692
+        if ($searchQuery->getOffset()) {
693
+            $query->setFirstResult($searchQuery->getOffset());
694
+        }
695
+
696
+        $result = $query->execute();
697
+        return $this->searchResultToCacheEntries($result);
698
+    }
699
+
700
+    /**
701
+     * Search for files by tag of a given users.
702
+     *
703
+     * Note that every user can tag files differently.
704
+     *
705
+     * @param string|int $tag name or tag id
706
+     * @param string $userId owner of the tags
707
+     * @return ICacheEntry[] file data
708
+     */
709
+    public function searchByTag($tag, $userId) {
710
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
711
+            '`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
712
+            '`encrypted`, `etag`, `permissions`, `checksum` ' .
713
+            'FROM `*PREFIX*filecache` `file`, ' .
714
+            '`*PREFIX*vcategory_to_object` `tagmap`, ' .
715
+            '`*PREFIX*vcategory` `tag` ' .
716
+            // JOIN filecache to vcategory_to_object
717
+            'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
718
+            // JOIN vcategory_to_object to vcategory
719
+            'AND `tagmap`.`type` = `tag`.`type` ' .
720
+            'AND `tagmap`.`categoryid` = `tag`.`id` ' .
721
+            // conditions
722
+            'AND `file`.`storage` = ? ' .
723
+            'AND `tag`.`type` = \'files\' ' .
724
+            'AND `tag`.`uid` = ? ';
725
+        if (is_int($tag)) {
726
+            $sql .= 'AND `tag`.`id` = ? ';
727
+        } else {
728
+            $sql .= 'AND `tag`.`category` = ? ';
729
+        }
730
+        $result = $this->connection->executeQuery(
731
+            $sql,
732
+            [
733
+                $this->getNumericStorageId(),
734
+                $userId,
735
+                $tag
736
+            ]
737
+        );
738
+
739
+        $files = $result->fetchAll();
740
+
741
+        return array_map(function (array $data) {
742
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
743
+        }, $files);
744
+    }
745
+
746
+    /**
747
+     * Re-calculate the folder size and the size of all parent folders
748
+     *
749
+     * @param string|boolean $path
750
+     * @param array $data (optional) meta data of the folder
751
+     */
752
+    public function correctFolderSize($path, $data = null) {
753
+        $this->calculateFolderSize($path, $data);
754
+        if ($path !== '') {
755
+            $parent = dirname($path);
756
+            if ($parent === '.' or $parent === '/') {
757
+                $parent = '';
758
+            }
759
+            $this->correctFolderSize($parent);
760
+        }
761
+    }
762
+
763
+    /**
764
+     * calculate the size of a folder and set it in the cache
765
+     *
766
+     * @param string $path
767
+     * @param array $entry (optional) meta data of the folder
768
+     * @return int
769
+     */
770
+    public function calculateFolderSize($path, $entry = null) {
771
+        $totalSize = 0;
772
+        if (is_null($entry) or !isset($entry['fileid'])) {
773
+            $entry = $this->get($path);
774
+        }
775
+        if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
776
+            $id = $entry['fileid'];
777
+            $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
778
+                'FROM `*PREFIX*filecache` ' .
779
+                'WHERE `parent` = ? AND `storage` = ?';
780
+            $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
781
+            if ($row = $result->fetch()) {
782
+                $result->closeCursor();
783
+                list($sum, $min) = array_values($row);
784
+                $sum = 0 + $sum;
785
+                $min = 0 + $min;
786
+                if ($min === -1) {
787
+                    $totalSize = $min;
788
+                } else {
789
+                    $totalSize = $sum;
790
+                }
791
+                $update = array();
792
+                if ($entry['size'] !== $totalSize) {
793
+                    $update['size'] = $totalSize;
794
+                }
795
+                if (count($update) > 0) {
796
+                    $this->update($id, $update);
797
+                }
798
+            } else {
799
+                $result->closeCursor();
800
+            }
801
+        }
802
+        return $totalSize;
803
+    }
804
+
805
+    /**
806
+     * get all file ids on the files on the storage
807
+     *
808
+     * @return int[]
809
+     */
810
+    public function getAll() {
811
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
812
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
813
+        $ids = array();
814
+        while ($row = $result->fetch()) {
815
+            $ids[] = $row['fileid'];
816
+        }
817
+        return $ids;
818
+    }
819
+
820
+    /**
821
+     * find a folder in the cache which has not been fully scanned
822
+     *
823
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
824
+     * use the one with the highest id gives the best result with the background scanner, since that is most
825
+     * likely the folder where we stopped scanning previously
826
+     *
827
+     * @return string|bool the path of the folder or false when no folder matched
828
+     */
829
+    public function getIncomplete() {
830
+        $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
831
+            . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
832
+        $query->execute([$this->getNumericStorageId()]);
833
+        if ($row = $query->fetch()) {
834
+            return $row['path'];
835
+        } else {
836
+            return false;
837
+        }
838
+    }
839
+
840
+    /**
841
+     * get the path of a file on this storage by it's file id
842
+     *
843
+     * @param int $id the file id of the file or folder to search
844
+     * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
845
+     */
846
+    public function getPathById($id) {
847
+        $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
848
+        $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
849
+        if ($row = $result->fetch()) {
850
+            // Oracle stores empty strings as null...
851
+            if ($row['path'] === null) {
852
+                return '';
853
+            }
854
+            return $row['path'];
855
+        } else {
856
+            return null;
857
+        }
858
+    }
859
+
860
+    /**
861
+     * get the storage id of the storage for a file and the internal path of the file
862
+     * unlike getPathById this does not limit the search to files on this storage and
863
+     * instead does a global search in the cache table
864
+     *
865
+     * @param int $id
866
+     * @deprecated use getPathById() instead
867
+     * @return array first element holding the storage id, second the path
868
+     */
869
+    static public function getById($id) {
870
+        $connection = \OC::$server->getDatabaseConnection();
871
+        $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
872
+        $result = $connection->executeQuery($sql, array($id));
873
+        if ($row = $result->fetch()) {
874
+            $numericId = $row['storage'];
875
+            $path = $row['path'];
876
+        } else {
877
+            return null;
878
+        }
879
+
880
+        if ($id = Storage::getStorageId($numericId)) {
881
+            return array($id, $path);
882
+        } else {
883
+            return null;
884
+        }
885
+    }
886
+
887
+    /**
888
+     * normalize the given path
889
+     *
890
+     * @param string $path
891
+     * @return string
892
+     */
893
+    public function normalize($path) {
894
+
895
+        return trim(\OC_Util::normalizeUnicode($path), '/');
896
+    }
897 897
 }
Please login to merge, or discard this patch.
Spacing   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -163,21 +163,21 @@  discard block
 block discarded – undo
163 163
 	 */
164 164
 	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
165 165
 		//fix types
166
-		$data['fileid'] = (int)$data['fileid'];
167
-		$data['parent'] = (int)$data['parent'];
166
+		$data['fileid'] = (int) $data['fileid'];
167
+		$data['parent'] = (int) $data['parent'];
168 168
 		$data['size'] = 0 + $data['size'];
169
-		$data['mtime'] = (int)$data['mtime'];
170
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
171
-		$data['encryptedVersion'] = (int)$data['encrypted'];
172
-		$data['encrypted'] = (bool)$data['encrypted'];
169
+		$data['mtime'] = (int) $data['mtime'];
170
+		$data['storage_mtime'] = (int) $data['storage_mtime'];
171
+		$data['encryptedVersion'] = (int) $data['encrypted'];
172
+		$data['encrypted'] = (bool) $data['encrypted'];
173 173
 		$data['storage_id'] = $data['storage'];
174
-		$data['storage'] = (int)$data['storage'];
174
+		$data['storage'] = (int) $data['storage'];
175 175
 		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
176 176
 		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
177 177
 		if ($data['storage_mtime'] == 0) {
178 178
 			$data['storage_mtime'] = $data['mtime'];
179 179
 		}
180
-		$data['permissions'] = (int)$data['permissions'];
180
+		$data['permissions'] = (int) $data['permissions'];
181 181
 		return new CacheEntry($data);
182 182
 	}
183 183
 
@@ -205,8 +205,8 @@  discard block
 block discarded – undo
205 205
 					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
206 206
 			$result = $this->connection->executeQuery($sql, [$fileId]);
207 207
 			$files = $result->fetchAll();
208
-			return array_map(function (array $data) {
209
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);;
208
+			return array_map(function(array $data) {
209
+				return self::cacheEntryFromData($data, $this->mimetypeLoader); ;
210 210
 			}, $files);
211 211
 		}
212 212
 		return [];
@@ -264,7 +264,7 @@  discard block
 block discarded – undo
264 264
 		$queryParts[] = '`storage`';
265 265
 		$params[] = $this->getNumericStorageId();
266 266
 
267
-		$queryParts = array_map(function ($item) {
267
+		$queryParts = array_map(function($item) {
268 268
 			return trim($item, "`");
269 269
 		}, $queryParts);
270 270
 		$values = array_combine($queryParts, $params);
@@ -273,7 +273,7 @@  discard block
 block discarded – undo
273 273
 			'path_hash',
274 274
 		])
275 275
 		) {
276
-			return (int)$this->connection->lastInsertId('*PREFIX*filecache');
276
+			return (int) $this->connection->lastInsertId('*PREFIX*filecache');
277 277
 		}
278 278
 
279 279
 		// The file was created in the mean time
@@ -311,10 +311,10 @@  discard block
 block discarded – undo
311 311
 
312 312
 		// don't update if the data we try to set is the same as the one in the record
313 313
 		// some databases (Postgres) don't like superfluous updates
314
-		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
315
-			'WHERE (' .
316
-			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
317
-			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
314
+		$sql = 'UPDATE `*PREFIX*filecache` SET '.implode(' = ?, ', $queryParts).'=? '.
315
+			'WHERE ('.
316
+			implode(' <> ? OR ', $queryParts).' <> ? OR '.
317
+			implode(' IS NULL OR ', $queryParts).' IS NULL'.
318 318
 			') AND `fileid` = ? ';
319 319
 		$this->connection->executeQuery($sql, $params);
320 320
 
@@ -365,7 +365,7 @@  discard block
 block discarded – undo
365 365
 					}
366 366
 				}
367 367
 				$params[] = $value;
368
-				$queryParts[] = '`' . $name . '`';
368
+				$queryParts[] = '`'.$name.'`';
369 369
 			}
370 370
 		}
371 371
 		return array($queryParts, $params);
@@ -407,7 +407,7 @@  discard block
 block discarded – undo
407 407
 			return -1;
408 408
 		} else {
409 409
 			$parent = $this->getParentPath($file);
410
-			return (int)$this->getId($parent);
410
+			return (int) $this->getId($parent);
411 411
 		}
412 412
 	}
413 413
 
@@ -453,7 +453,7 @@  discard block
 block discarded – undo
453 453
 	 */
454 454
 	private function getSubFolders($entry) {
455 455
 		$children = $this->getFolderContentsById($entry['fileid']);
456
-		return array_filter($children, function ($child) {
456
+		return array_filter($children, function($child) {
457 457
 			return $child['mimetype'] === 'httpd/unix-directory';
458 458
 		});
459 459
 	}
@@ -517,10 +517,10 @@  discard block
 block discarded – undo
517 517
 			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
518 518
 
519 519
 			if (is_null($sourceStorageId) || $sourceStorageId === false) {
520
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
520
+				throw new \Exception('Invalid source storage id: '.$sourceStorageId);
521 521
 			}
522 522
 			if (is_null($targetStorageId) || $targetStorageId === false) {
523
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
523
+				throw new \Exception('Invalid target storage id: '.$targetStorageId);
524 524
 			}
525 525
 
526 526
 			$this->connection->beginTransaction();
@@ -539,7 +539,7 @@  discard block
 block discarded – undo
539 539
 					->set('path_hash', $fun->md5($newPathFunction))
540 540
 					->set('path', $newPathFunction)
541 541
 					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
542
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
542
+					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath).'/%')));
543 543
 
544 544
 				try {
545 545
 					$query->execute();
@@ -588,7 +588,7 @@  discard block
 block discarded – undo
588 588
 		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
589 589
 		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
590 590
 		if ($row = $result->fetch()) {
591
-			if ((int)$row['size'] === -1) {
591
+			if ((int) $row['size'] === -1) {
592 592
 				return self::SHALLOW;
593 593
 			} else {
594 594
 				return self::COMPLETE;
@@ -637,7 +637,7 @@  discard block
 block discarded – undo
637 637
 	private function searchResultToCacheEntries(Statement $result) {
638 638
 		$files = $result->fetchAll();
639 639
 
640
-		return array_map(function (array $data) {
640
+		return array_map(function(array $data) {
641 641
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
642 642
 		}, $files);
643 643
 	}
@@ -656,7 +656,7 @@  discard block
 block discarded – undo
656 656
 			$where = '`mimepart` = ?';
657 657
 		}
658 658
 		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
659
-				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
659
+				FROM `*PREFIX*filecache` WHERE ' . $where.' AND `storage` = ?';
660 660
 		$mimetype = $this->mimetypeLoader->getId($mimetype);
661 661
 		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
662 662
 
@@ -707,20 +707,20 @@  discard block
 block discarded – undo
707 707
 	 * @return ICacheEntry[] file data
708 708
 	 */
709 709
 	public function searchByTag($tag, $userId) {
710
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
711
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
712
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
713
-			'FROM `*PREFIX*filecache` `file`, ' .
714
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
715
-			'`*PREFIX*vcategory` `tag` ' .
710
+		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, '.
711
+			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, '.
712
+			'`encrypted`, `etag`, `permissions`, `checksum` '.
713
+			'FROM `*PREFIX*filecache` `file`, '.
714
+			'`*PREFIX*vcategory_to_object` `tagmap`, '.
715
+			'`*PREFIX*vcategory` `tag` '.
716 716
 			// JOIN filecache to vcategory_to_object
717
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
717
+			'WHERE `file`.`fileid` = `tagmap`.`objid` '.
718 718
 			// JOIN vcategory_to_object to vcategory
719
-			'AND `tagmap`.`type` = `tag`.`type` ' .
720
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
719
+			'AND `tagmap`.`type` = `tag`.`type` '.
720
+			'AND `tagmap`.`categoryid` = `tag`.`id` '.
721 721
 			// conditions
722
-			'AND `file`.`storage` = ? ' .
723
-			'AND `tag`.`type` = \'files\' ' .
722
+			'AND `file`.`storage` = ? '.
723
+			'AND `tag`.`type` = \'files\' '.
724 724
 			'AND `tag`.`uid` = ? ';
725 725
 		if (is_int($tag)) {
726 726
 			$sql .= 'AND `tag`.`id` = ? ';
@@ -738,7 +738,7 @@  discard block
 block discarded – undo
738 738
 
739 739
 		$files = $result->fetchAll();
740 740
 
741
-		return array_map(function (array $data) {
741
+		return array_map(function(array $data) {
742 742
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
743 743
 		}, $files);
744 744
 	}
@@ -774,8 +774,8 @@  discard block
 block discarded – undo
774 774
 		}
775 775
 		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
776 776
 			$id = $entry['fileid'];
777
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
778
-				'FROM `*PREFIX*filecache` ' .
777
+			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 '.
778
+				'FROM `*PREFIX*filecache` '.
779 779
 				'WHERE `parent` = ? AND `storage` = ?';
780 780
 			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
781 781
 			if ($row = $result->fetch()) {
Please login to merge, or discard this patch.
lib/private/User/Session.php 2 patches
Indentation   +824 added lines, -824 removed lines patch added patch discarded remove patch
@@ -84,830 +84,830 @@
 block discarded – undo
84 84
  */
85 85
 class Session implements IUserSession, Emitter {
86 86
 
87
-	/** @var Manager|PublicEmitter $manager */
88
-	private $manager;
89
-
90
-	/** @var ISession $session */
91
-	private $session;
92
-
93
-	/** @var ITimeFactory */
94
-	private $timeFactory;
95
-
96
-	/** @var IProvider */
97
-	private $tokenProvider;
98
-
99
-	/** @var IConfig */
100
-	private $config;
101
-
102
-	/** @var User $activeUser */
103
-	protected $activeUser;
104
-
105
-	/** @var ISecureRandom */
106
-	private $random;
107
-
108
-	/** @var ILockdownManager  */
109
-	private $lockdownManager;
110
-
111
-	/** @var ILogger */
112
-	private $logger;
113
-
114
-	/**
115
-	 * @param Manager $manager
116
-	 * @param ISession $session
117
-	 * @param ITimeFactory $timeFactory
118
-	 * @param IProvider $tokenProvider
119
-	 * @param IConfig $config
120
-	 * @param ISecureRandom $random
121
-	 * @param ILockdownManager $lockdownManager
122
-	 * @param ILogger $logger
123
-	 */
124
-	public function __construct(Manager $manager,
125
-								ISession $session,
126
-								ITimeFactory $timeFactory,
127
-								$tokenProvider,
128
-								IConfig $config,
129
-								ISecureRandom $random,
130
-								ILockdownManager $lockdownManager,
131
-								ILogger $logger) {
132
-		$this->manager = $manager;
133
-		$this->session = $session;
134
-		$this->timeFactory = $timeFactory;
135
-		$this->tokenProvider = $tokenProvider;
136
-		$this->config = $config;
137
-		$this->random = $random;
138
-		$this->lockdownManager = $lockdownManager;
139
-		$this->logger = $logger;
140
-	}
141
-
142
-	/**
143
-	 * @param IProvider $provider
144
-	 */
145
-	public function setTokenProvider(IProvider $provider) {
146
-		$this->tokenProvider = $provider;
147
-	}
148
-
149
-	/**
150
-	 * @param string $scope
151
-	 * @param string $method
152
-	 * @param callable $callback
153
-	 */
154
-	public function listen($scope, $method, callable $callback) {
155
-		$this->manager->listen($scope, $method, $callback);
156
-	}
157
-
158
-	/**
159
-	 * @param string $scope optional
160
-	 * @param string $method optional
161
-	 * @param callable $callback optional
162
-	 */
163
-	public function removeListener($scope = null, $method = null, callable $callback = null) {
164
-		$this->manager->removeListener($scope, $method, $callback);
165
-	}
166
-
167
-	/**
168
-	 * get the manager object
169
-	 *
170
-	 * @return Manager|PublicEmitter
171
-	 */
172
-	public function getManager() {
173
-		return $this->manager;
174
-	}
175
-
176
-	/**
177
-	 * get the session object
178
-	 *
179
-	 * @return ISession
180
-	 */
181
-	public function getSession() {
182
-		return $this->session;
183
-	}
184
-
185
-	/**
186
-	 * set the session object
187
-	 *
188
-	 * @param ISession $session
189
-	 */
190
-	public function setSession(ISession $session) {
191
-		if ($this->session instanceof ISession) {
192
-			$this->session->close();
193
-		}
194
-		$this->session = $session;
195
-		$this->activeUser = null;
196
-	}
197
-
198
-	/**
199
-	 * set the currently active user
200
-	 *
201
-	 * @param IUser|null $user
202
-	 */
203
-	public function setUser($user) {
204
-		if (is_null($user)) {
205
-			$this->session->remove('user_id');
206
-		} else {
207
-			$this->session->set('user_id', $user->getUID());
208
-		}
209
-		$this->activeUser = $user;
210
-	}
211
-
212
-	/**
213
-	 * get the current active user
214
-	 *
215
-	 * @return IUser|null Current user, otherwise null
216
-	 */
217
-	public function getUser() {
218
-		// FIXME: This is a quick'n dirty work-around for the incognito mode as
219
-		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
220
-		if (OC_User::isIncognitoMode()) {
221
-			return null;
222
-		}
223
-		if (is_null($this->activeUser)) {
224
-			$uid = $this->session->get('user_id');
225
-			if (is_null($uid)) {
226
-				return null;
227
-			}
228
-			$this->activeUser = $this->manager->get($uid);
229
-			if (is_null($this->activeUser)) {
230
-				return null;
231
-			}
232
-			$this->validateSession();
233
-		}
234
-		return $this->activeUser;
235
-	}
236
-
237
-	/**
238
-	 * Validate whether the current session is valid
239
-	 *
240
-	 * - For token-authenticated clients, the token validity is checked
241
-	 * - For browsers, the session token validity is checked
242
-	 */
243
-	protected function validateSession() {
244
-		$token = null;
245
-		$appPassword = $this->session->get('app_password');
246
-
247
-		if (is_null($appPassword)) {
248
-			try {
249
-				$token = $this->session->getId();
250
-			} catch (SessionNotAvailableException $ex) {
251
-				return;
252
-			}
253
-		} else {
254
-			$token = $appPassword;
255
-		}
256
-
257
-		if (!$this->validateToken($token)) {
258
-			// Session was invalidated
259
-			$this->logout();
260
-		}
261
-	}
262
-
263
-	/**
264
-	 * Checks whether the user is logged in
265
-	 *
266
-	 * @return bool if logged in
267
-	 */
268
-	public function isLoggedIn() {
269
-		$user = $this->getUser();
270
-		if (is_null($user)) {
271
-			return false;
272
-		}
273
-
274
-		return $user->isEnabled();
275
-	}
276
-
277
-	/**
278
-	 * set the login name
279
-	 *
280
-	 * @param string|null $loginName for the logged in user
281
-	 */
282
-	public function setLoginName($loginName) {
283
-		if (is_null($loginName)) {
284
-			$this->session->remove('loginname');
285
-		} else {
286
-			$this->session->set('loginname', $loginName);
287
-		}
288
-	}
289
-
290
-	/**
291
-	 * get the login name of the current user
292
-	 *
293
-	 * @return string
294
-	 */
295
-	public function getLoginName() {
296
-		if ($this->activeUser) {
297
-			return $this->session->get('loginname');
298
-		}
299
-
300
-		$uid = $this->session->get('user_id');
301
-		if ($uid) {
302
-			$this->activeUser = $this->manager->get($uid);
303
-			return $this->session->get('loginname');
304
-		}
305
-
306
-		return null;
307
-	}
308
-
309
-	/**
310
-	 * set the token id
311
-	 *
312
-	 * @param int|null $token that was used to log in
313
-	 */
314
-	protected function setToken($token) {
315
-		if ($token === null) {
316
-			$this->session->remove('token-id');
317
-		} else {
318
-			$this->session->set('token-id', $token);
319
-		}
320
-	}
321
-
322
-	/**
323
-	 * try to log in with the provided credentials
324
-	 *
325
-	 * @param string $uid
326
-	 * @param string $password
327
-	 * @return boolean|null
328
-	 * @throws LoginException
329
-	 */
330
-	public function login($uid, $password) {
331
-		$this->session->regenerateId();
332
-		if ($this->validateToken($password, $uid)) {
333
-			return $this->loginWithToken($password);
334
-		}
335
-		return $this->loginWithPassword($uid, $password);
336
-	}
337
-
338
-	/**
339
-	 * @param IUser $user
340
-	 * @param array $loginDetails
341
-	 * @param bool $regenerateSessionId
342
-	 * @return true returns true if login successful or an exception otherwise
343
-	 * @throws LoginException
344
-	 */
345
-	public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
346
-		if (!$user->isEnabled()) {
347
-			// disabled users can not log in
348
-			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
349
-			$message = \OC::$server->getL10N('lib')->t('User disabled');
350
-			throw new LoginException($message);
351
-		}
352
-
353
-		if($regenerateSessionId) {
354
-			$this->session->regenerateId();
355
-		}
356
-
357
-		$this->setUser($user);
358
-		$this->setLoginName($loginDetails['loginName']);
359
-
360
-		if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) {
361
-			$this->setToken($loginDetails['token']->getId());
362
-			$this->lockdownManager->setToken($loginDetails['token']);
363
-			$firstTimeLogin = false;
364
-		} else {
365
-			$this->setToken(null);
366
-			$firstTimeLogin = $user->updateLastLoginTimestamp();
367
-		}
368
-		$this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]);
369
-		if($this->isLoggedIn()) {
370
-			$this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
371
-			return true;
372
-		}
373
-
374
-		$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
375
-		throw new LoginException($message);
376
-	}
377
-
378
-	/**
379
-	 * Tries to log in a client
380
-	 *
381
-	 * Checks token auth enforced
382
-	 * Checks 2FA enabled
383
-	 *
384
-	 * @param string $user
385
-	 * @param string $password
386
-	 * @param IRequest $request
387
-	 * @param OC\Security\Bruteforce\Throttler $throttler
388
-	 * @throws LoginException
389
-	 * @throws PasswordLoginForbiddenException
390
-	 * @return boolean
391
-	 */
392
-	public function logClientIn($user,
393
-								$password,
394
-								IRequest $request,
395
-								OC\Security\Bruteforce\Throttler $throttler) {
396
-		$currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
397
-
398
-		if ($this->manager instanceof PublicEmitter) {
399
-			$this->manager->emit('\OC\User', 'preLogin', array($user, $password));
400
-		}
401
-
402
-		$isTokenPassword = $this->isTokenPassword($password);
403
-		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
404
-			throw new PasswordLoginForbiddenException();
405
-		}
406
-		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
407
-			throw new PasswordLoginForbiddenException();
408
-		}
409
-
410
-		// Try to login with this username and password
411
-		if (!$this->login($user, $password) ) {
412
-
413
-			// Failed, maybe the user used their email address
414
-			$users = $this->manager->getByEmail($user);
415
-			if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
416
-
417
-				$this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
418
-
419
-				$throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]);
420
-				if ($currentDelay === 0) {
421
-					$throttler->sleepDelay($request->getRemoteAddress(), 'login');
422
-				}
423
-				return false;
424
-			}
425
-		}
426
-
427
-		if ($isTokenPassword) {
428
-			$this->session->set('app_password', $password);
429
-		} else if($this->supportsCookies($request)) {
430
-			// Password login, but cookies supported -> create (browser) session token
431
-			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
432
-		}
433
-
434
-		return true;
435
-	}
436
-
437
-	protected function supportsCookies(IRequest $request) {
438
-		if (!is_null($request->getCookie('cookie_test'))) {
439
-			return true;
440
-		}
441
-		setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
442
-		return false;
443
-	}
444
-
445
-	private function isTokenAuthEnforced() {
446
-		return $this->config->getSystemValue('token_auth_enforced', false);
447
-	}
448
-
449
-	protected function isTwoFactorEnforced($username) {
450
-		Util::emitHook(
451
-			'\OCA\Files_Sharing\API\Server2Server',
452
-			'preLoginNameUsedAsUserName',
453
-			array('uid' => &$username)
454
-		);
455
-		$user = $this->manager->get($username);
456
-		if (is_null($user)) {
457
-			$users = $this->manager->getByEmail($username);
458
-			if (empty($users)) {
459
-				return false;
460
-			}
461
-			if (count($users) !== 1) {
462
-				return true;
463
-			}
464
-			$user = $users[0];
465
-		}
466
-		// DI not possible due to cyclic dependencies :'-/
467
-		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
468
-	}
469
-
470
-	/**
471
-	 * Check if the given 'password' is actually a device token
472
-	 *
473
-	 * @param string $password
474
-	 * @return boolean
475
-	 */
476
-	public function isTokenPassword($password) {
477
-		try {
478
-			$this->tokenProvider->getToken($password);
479
-			return true;
480
-		} catch (InvalidTokenException $ex) {
481
-			return false;
482
-		}
483
-	}
484
-
485
-	protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
486
-		if ($refreshCsrfToken) {
487
-			// TODO: mock/inject/use non-static
488
-			// Refresh the token
489
-			\OC::$server->getCsrfTokenManager()->refreshToken();
490
-		}
491
-
492
-		//we need to pass the user name, which may differ from login name
493
-		$user = $this->getUser()->getUID();
494
-		OC_Util::setupFS($user);
495
-
496
-		if ($firstTimeLogin) {
497
-			// TODO: lock necessary?
498
-			//trigger creation of user home and /files folder
499
-			$userFolder = \OC::$server->getUserFolder($user);
500
-
501
-			try {
502
-				// copy skeleton
503
-				\OC_Util::copySkeleton($user, $userFolder);
504
-			} catch (NotPermittedException $ex) {
505
-				// read only uses
506
-			}
507
-
508
-			// trigger any other initialization
509
-			\OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
510
-		}
511
-	}
512
-
513
-	/**
514
-	 * Tries to login the user with HTTP Basic Authentication
515
-	 *
516
-	 * @todo do not allow basic auth if the user is 2FA enforced
517
-	 * @param IRequest $request
518
-	 * @param OC\Security\Bruteforce\Throttler $throttler
519
-	 * @return boolean if the login was successful
520
-	 */
521
-	public function tryBasicAuthLogin(IRequest $request,
522
-									  OC\Security\Bruteforce\Throttler $throttler) {
523
-		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
524
-			try {
525
-				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
526
-					/**
527
-					 * Add DAV authenticated. This should in an ideal world not be
528
-					 * necessary but the iOS App reads cookies from anywhere instead
529
-					 * only the DAV endpoint.
530
-					 * This makes sure that the cookies will be valid for the whole scope
531
-					 * @see https://github.com/owncloud/core/issues/22893
532
-					 */
533
-					$this->session->set(
534
-						Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
535
-					);
536
-
537
-					// Set the last-password-confirm session to make the sudo mode work
538
-					 $this->session->set('last-password-confirm', $this->timeFactory->getTime());
539
-
540
-					return true;
541
-				}
542
-			} catch (PasswordLoginForbiddenException $ex) {
543
-				// Nothing to do
544
-			}
545
-		}
546
-		return false;
547
-	}
548
-
549
-	/**
550
-	 * Log an user in via login name and password
551
-	 *
552
-	 * @param string $uid
553
-	 * @param string $password
554
-	 * @return boolean
555
-	 * @throws LoginException if an app canceld the login process or the user is not enabled
556
-	 */
557
-	private function loginWithPassword($uid, $password) {
558
-		$user = $this->manager->checkPasswordNoLogging($uid, $password);
559
-		if ($user === false) {
560
-			// Password check failed
561
-			return false;
562
-		}
563
-
564
-		return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
565
-	}
566
-
567
-	/**
568
-	 * Log an user in with a given token (id)
569
-	 *
570
-	 * @param string $token
571
-	 * @return boolean
572
-	 * @throws LoginException if an app canceled the login process or the user is not enabled
573
-	 */
574
-	private function loginWithToken($token) {
575
-		try {
576
-			$dbToken = $this->tokenProvider->getToken($token);
577
-		} catch (InvalidTokenException $ex) {
578
-			return false;
579
-		}
580
-		$uid = $dbToken->getUID();
581
-
582
-		// When logging in with token, the password must be decrypted first before passing to login hook
583
-		$password = '';
584
-		try {
585
-			$password = $this->tokenProvider->getPassword($dbToken, $token);
586
-		} catch (PasswordlessTokenException $ex) {
587
-			// Ignore and use empty string instead
588
-		}
589
-
590
-		$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
591
-
592
-		$user = $this->manager->get($uid);
593
-		if (is_null($user)) {
594
-			// user does not exist
595
-			return false;
596
-		}
597
-
598
-		return $this->completeLogin(
599
-			$user,
600
-			[
601
-				'loginName' => $dbToken->getLoginName(),
602
-				'password' => $password,
603
-				'token' => $dbToken
604
-			],
605
-			false);
606
-	}
607
-
608
-	/**
609
-	 * Create a new session token for the given user credentials
610
-	 *
611
-	 * @param IRequest $request
612
-	 * @param string $uid user UID
613
-	 * @param string $loginName login name
614
-	 * @param string $password
615
-	 * @param int $remember
616
-	 * @return boolean
617
-	 */
618
-	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
619
-		if (is_null($this->manager->get($uid))) {
620
-			// User does not exist
621
-			return false;
622
-		}
623
-		$name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
624
-		try {
625
-			$sessionId = $this->session->getId();
626
-			$pwd = $this->getPassword($password);
627
-			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
628
-			return true;
629
-		} catch (SessionNotAvailableException $ex) {
630
-			// This can happen with OCC, where a memory session is used
631
-			// if a memory session is used, we shouldn't create a session token anyway
632
-			return false;
633
-		}
634
-	}
635
-
636
-	/**
637
-	 * Checks if the given password is a token.
638
-	 * If yes, the password is extracted from the token.
639
-	 * If no, the same password is returned.
640
-	 *
641
-	 * @param string $password either the login password or a device token
642
-	 * @return string|null the password or null if none was set in the token
643
-	 */
644
-	private function getPassword($password) {
645
-		if (is_null($password)) {
646
-			// This is surely no token ;-)
647
-			return null;
648
-		}
649
-		try {
650
-			$token = $this->tokenProvider->getToken($password);
651
-			try {
652
-				return $this->tokenProvider->getPassword($token, $password);
653
-			} catch (PasswordlessTokenException $ex) {
654
-				return null;
655
-			}
656
-		} catch (InvalidTokenException $ex) {
657
-			return $password;
658
-		}
659
-	}
660
-
661
-	/**
662
-	 * @param IToken $dbToken
663
-	 * @param string $token
664
-	 * @return boolean
665
-	 */
666
-	private function checkTokenCredentials(IToken $dbToken, $token) {
667
-		// Check whether login credentials are still valid and the user was not disabled
668
-		// This check is performed each 5 minutes
669
-		$lastCheck = $dbToken->getLastCheck() ? : 0;
670
-		$now = $this->timeFactory->getTime();
671
-		if ($lastCheck > ($now - 60 * 5)) {
672
-			// Checked performed recently, nothing to do now
673
-			return true;
674
-		}
675
-
676
-		try {
677
-			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
678
-		} catch (InvalidTokenException $ex) {
679
-			// An invalid token password was used -> log user out
680
-			return false;
681
-		} catch (PasswordlessTokenException $ex) {
682
-			// Token has no password
683
-
684
-			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
685
-				$this->tokenProvider->invalidateToken($token);
686
-				return false;
687
-			}
688
-
689
-			$dbToken->setLastCheck($now);
690
-			return true;
691
-		}
692
-
693
-		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
694
-			|| (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
695
-			$this->tokenProvider->invalidateToken($token);
696
-			// Password has changed or user was disabled -> log user out
697
-			return false;
698
-		}
699
-		$dbToken->setLastCheck($now);
700
-		return true;
701
-	}
702
-
703
-	/**
704
-	 * Check if the given token exists and performs password/user-enabled checks
705
-	 *
706
-	 * Invalidates the token if checks fail
707
-	 *
708
-	 * @param string $token
709
-	 * @param string $user login name
710
-	 * @return boolean
711
-	 */
712
-	private function validateToken($token, $user = null) {
713
-		try {
714
-			$dbToken = $this->tokenProvider->getToken($token);
715
-		} catch (InvalidTokenException $ex) {
716
-			return false;
717
-		}
718
-
719
-		// Check if login names match
720
-		if (!is_null($user) && $dbToken->getLoginName() !== $user) {
721
-			// TODO: this makes it imposssible to use different login names on browser and client
722
-			// e.g. login by e-mail '[email protected]' on browser for generating the token will not
723
-			//      allow to use the client token with the login name 'user'.
724
-			return false;
725
-		}
726
-
727
-		if (!$this->checkTokenCredentials($dbToken, $token)) {
728
-			return false;
729
-		}
730
-
731
-		$this->tokenProvider->updateTokenActivity($dbToken);
732
-
733
-		return true;
734
-	}
735
-
736
-	/**
737
-	 * Tries to login the user with auth token header
738
-	 *
739
-	 * @param IRequest $request
740
-	 * @todo check remember me cookie
741
-	 * @return boolean
742
-	 */
743
-	public function tryTokenLogin(IRequest $request) {
744
-		$authHeader = $request->getHeader('Authorization');
745
-		if (strpos($authHeader, 'Bearer ') === false) {
746
-			// No auth header, let's try session id
747
-			try {
748
-				$token = $this->session->getId();
749
-			} catch (SessionNotAvailableException $ex) {
750
-				return false;
751
-			}
752
-		} else {
753
-			$token = substr($authHeader, 7);
754
-		}
755
-
756
-		if (!$this->loginWithToken($token)) {
757
-			return false;
758
-		}
759
-		if(!$this->validateToken($token)) {
760
-			return false;
761
-		}
762
-		return true;
763
-	}
764
-
765
-	/**
766
-	 * perform login using the magic cookie (remember login)
767
-	 *
768
-	 * @param string $uid the username
769
-	 * @param string $currentToken
770
-	 * @param string $oldSessionId
771
-	 * @return bool
772
-	 */
773
-	public function loginWithCookie($uid, $currentToken, $oldSessionId) {
774
-		$this->session->regenerateId();
775
-		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
776
-		$user = $this->manager->get($uid);
777
-		if (is_null($user)) {
778
-			// user does not exist
779
-			return false;
780
-		}
781
-
782
-		// get stored tokens
783
-		$tokens = $this->config->getUserKeys($uid, 'login_token');
784
-		// test cookies token against stored tokens
785
-		if (!in_array($currentToken, $tokens, true)) {
786
-			return false;
787
-		}
788
-		// replace successfully used token with a new one
789
-		$this->config->deleteUserValue($uid, 'login_token', $currentToken);
790
-		$newToken = $this->random->generate(32);
791
-		$this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
792
-
793
-		try {
794
-			$sessionId = $this->session->getId();
795
-			$this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
796
-		} catch (SessionNotAvailableException $ex) {
797
-			return false;
798
-		} catch (InvalidTokenException $ex) {
799
-			\OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
800
-			return false;
801
-		}
802
-
803
-		$this->setMagicInCookie($user->getUID(), $newToken);
804
-		$token = $this->tokenProvider->getToken($sessionId);
805
-
806
-		//login
807
-		$this->setUser($user);
808
-		$this->setLoginName($token->getLoginName());
809
-		$this->setToken($token->getId());
810
-		$this->lockdownManager->setToken($token);
811
-		$user->updateLastLoginTimestamp();
812
-		$password = null;
813
-		try {
814
-			$password = $this->tokenProvider->getPassword($token, $sessionId);
815
-		} catch (PasswordlessTokenException $ex) {
816
-			// Ignore
817
-		}
818
-		$this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
819
-		return true;
820
-	}
821
-
822
-	/**
823
-	 * @param IUser $user
824
-	 */
825
-	public function createRememberMeToken(IUser $user) {
826
-		$token = $this->random->generate(32);
827
-		$this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
828
-		$this->setMagicInCookie($user->getUID(), $token);
829
-	}
830
-
831
-	/**
832
-	 * logout the user from the session
833
-	 */
834
-	public function logout() {
835
-		$this->manager->emit('\OC\User', 'logout');
836
-		$user = $this->getUser();
837
-		if (!is_null($user)) {
838
-			try {
839
-				$this->tokenProvider->invalidateToken($this->session->getId());
840
-			} catch (SessionNotAvailableException $ex) {
841
-
842
-			}
843
-		}
844
-		$this->setUser(null);
845
-		$this->setLoginName(null);
846
-		$this->setToken(null);
847
-		$this->unsetMagicInCookie();
848
-		$this->session->clear();
849
-		$this->manager->emit('\OC\User', 'postLogout');
850
-	}
851
-
852
-	/**
853
-	 * Set cookie value to use in next page load
854
-	 *
855
-	 * @param string $username username to be set
856
-	 * @param string $token
857
-	 */
858
-	public function setMagicInCookie($username, $token) {
859
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
860
-		$webRoot = \OC::$WEBROOT;
861
-		if ($webRoot === '') {
862
-			$webRoot = '/';
863
-		}
864
-
865
-		$expires = $this->timeFactory->getTime() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
866
-		setcookie('nc_username', $username, $expires, $webRoot, '', $secureCookie, true);
867
-		setcookie('nc_token', $token, $expires, $webRoot, '', $secureCookie, true);
868
-		try {
869
-			setcookie('nc_session_id', $this->session->getId(), $expires, $webRoot, '', $secureCookie, true);
870
-		} catch (SessionNotAvailableException $ex) {
871
-			// ignore
872
-		}
873
-	}
874
-
875
-	/**
876
-	 * Remove cookie for "remember username"
877
-	 */
878
-	public function unsetMagicInCookie() {
879
-		//TODO: DI for cookies and IRequest
880
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
881
-
882
-		unset($_COOKIE['nc_username']); //TODO: DI
883
-		unset($_COOKIE['nc_token']);
884
-		unset($_COOKIE['nc_session_id']);
885
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
886
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
887
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
888
-		// old cookies might be stored under /webroot/ instead of /webroot
889
-		// and Firefox doesn't like it!
890
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
891
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
892
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
893
-	}
894
-
895
-	/**
896
-	 * Update password of the browser session token if there is one
897
-	 *
898
-	 * @param string $password
899
-	 */
900
-	public function updateSessionTokenPassword($password) {
901
-		try {
902
-			$sessionId = $this->session->getId();
903
-			$token = $this->tokenProvider->getToken($sessionId);
904
-			$this->tokenProvider->setPassword($token, $sessionId, $password);
905
-		} catch (SessionNotAvailableException $ex) {
906
-			// Nothing to do
907
-		} catch (InvalidTokenException $ex) {
908
-			// Nothing to do
909
-		}
910
-	}
87
+    /** @var Manager|PublicEmitter $manager */
88
+    private $manager;
89
+
90
+    /** @var ISession $session */
91
+    private $session;
92
+
93
+    /** @var ITimeFactory */
94
+    private $timeFactory;
95
+
96
+    /** @var IProvider */
97
+    private $tokenProvider;
98
+
99
+    /** @var IConfig */
100
+    private $config;
101
+
102
+    /** @var User $activeUser */
103
+    protected $activeUser;
104
+
105
+    /** @var ISecureRandom */
106
+    private $random;
107
+
108
+    /** @var ILockdownManager  */
109
+    private $lockdownManager;
110
+
111
+    /** @var ILogger */
112
+    private $logger;
113
+
114
+    /**
115
+     * @param Manager $manager
116
+     * @param ISession $session
117
+     * @param ITimeFactory $timeFactory
118
+     * @param IProvider $tokenProvider
119
+     * @param IConfig $config
120
+     * @param ISecureRandom $random
121
+     * @param ILockdownManager $lockdownManager
122
+     * @param ILogger $logger
123
+     */
124
+    public function __construct(Manager $manager,
125
+                                ISession $session,
126
+                                ITimeFactory $timeFactory,
127
+                                $tokenProvider,
128
+                                IConfig $config,
129
+                                ISecureRandom $random,
130
+                                ILockdownManager $lockdownManager,
131
+                                ILogger $logger) {
132
+        $this->manager = $manager;
133
+        $this->session = $session;
134
+        $this->timeFactory = $timeFactory;
135
+        $this->tokenProvider = $tokenProvider;
136
+        $this->config = $config;
137
+        $this->random = $random;
138
+        $this->lockdownManager = $lockdownManager;
139
+        $this->logger = $logger;
140
+    }
141
+
142
+    /**
143
+     * @param IProvider $provider
144
+     */
145
+    public function setTokenProvider(IProvider $provider) {
146
+        $this->tokenProvider = $provider;
147
+    }
148
+
149
+    /**
150
+     * @param string $scope
151
+     * @param string $method
152
+     * @param callable $callback
153
+     */
154
+    public function listen($scope, $method, callable $callback) {
155
+        $this->manager->listen($scope, $method, $callback);
156
+    }
157
+
158
+    /**
159
+     * @param string $scope optional
160
+     * @param string $method optional
161
+     * @param callable $callback optional
162
+     */
163
+    public function removeListener($scope = null, $method = null, callable $callback = null) {
164
+        $this->manager->removeListener($scope, $method, $callback);
165
+    }
166
+
167
+    /**
168
+     * get the manager object
169
+     *
170
+     * @return Manager|PublicEmitter
171
+     */
172
+    public function getManager() {
173
+        return $this->manager;
174
+    }
175
+
176
+    /**
177
+     * get the session object
178
+     *
179
+     * @return ISession
180
+     */
181
+    public function getSession() {
182
+        return $this->session;
183
+    }
184
+
185
+    /**
186
+     * set the session object
187
+     *
188
+     * @param ISession $session
189
+     */
190
+    public function setSession(ISession $session) {
191
+        if ($this->session instanceof ISession) {
192
+            $this->session->close();
193
+        }
194
+        $this->session = $session;
195
+        $this->activeUser = null;
196
+    }
197
+
198
+    /**
199
+     * set the currently active user
200
+     *
201
+     * @param IUser|null $user
202
+     */
203
+    public function setUser($user) {
204
+        if (is_null($user)) {
205
+            $this->session->remove('user_id');
206
+        } else {
207
+            $this->session->set('user_id', $user->getUID());
208
+        }
209
+        $this->activeUser = $user;
210
+    }
211
+
212
+    /**
213
+     * get the current active user
214
+     *
215
+     * @return IUser|null Current user, otherwise null
216
+     */
217
+    public function getUser() {
218
+        // FIXME: This is a quick'n dirty work-around for the incognito mode as
219
+        // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
220
+        if (OC_User::isIncognitoMode()) {
221
+            return null;
222
+        }
223
+        if (is_null($this->activeUser)) {
224
+            $uid = $this->session->get('user_id');
225
+            if (is_null($uid)) {
226
+                return null;
227
+            }
228
+            $this->activeUser = $this->manager->get($uid);
229
+            if (is_null($this->activeUser)) {
230
+                return null;
231
+            }
232
+            $this->validateSession();
233
+        }
234
+        return $this->activeUser;
235
+    }
236
+
237
+    /**
238
+     * Validate whether the current session is valid
239
+     *
240
+     * - For token-authenticated clients, the token validity is checked
241
+     * - For browsers, the session token validity is checked
242
+     */
243
+    protected function validateSession() {
244
+        $token = null;
245
+        $appPassword = $this->session->get('app_password');
246
+
247
+        if (is_null($appPassword)) {
248
+            try {
249
+                $token = $this->session->getId();
250
+            } catch (SessionNotAvailableException $ex) {
251
+                return;
252
+            }
253
+        } else {
254
+            $token = $appPassword;
255
+        }
256
+
257
+        if (!$this->validateToken($token)) {
258
+            // Session was invalidated
259
+            $this->logout();
260
+        }
261
+    }
262
+
263
+    /**
264
+     * Checks whether the user is logged in
265
+     *
266
+     * @return bool if logged in
267
+     */
268
+    public function isLoggedIn() {
269
+        $user = $this->getUser();
270
+        if (is_null($user)) {
271
+            return false;
272
+        }
273
+
274
+        return $user->isEnabled();
275
+    }
276
+
277
+    /**
278
+     * set the login name
279
+     *
280
+     * @param string|null $loginName for the logged in user
281
+     */
282
+    public function setLoginName($loginName) {
283
+        if (is_null($loginName)) {
284
+            $this->session->remove('loginname');
285
+        } else {
286
+            $this->session->set('loginname', $loginName);
287
+        }
288
+    }
289
+
290
+    /**
291
+     * get the login name of the current user
292
+     *
293
+     * @return string
294
+     */
295
+    public function getLoginName() {
296
+        if ($this->activeUser) {
297
+            return $this->session->get('loginname');
298
+        }
299
+
300
+        $uid = $this->session->get('user_id');
301
+        if ($uid) {
302
+            $this->activeUser = $this->manager->get($uid);
303
+            return $this->session->get('loginname');
304
+        }
305
+
306
+        return null;
307
+    }
308
+
309
+    /**
310
+     * set the token id
311
+     *
312
+     * @param int|null $token that was used to log in
313
+     */
314
+    protected function setToken($token) {
315
+        if ($token === null) {
316
+            $this->session->remove('token-id');
317
+        } else {
318
+            $this->session->set('token-id', $token);
319
+        }
320
+    }
321
+
322
+    /**
323
+     * try to log in with the provided credentials
324
+     *
325
+     * @param string $uid
326
+     * @param string $password
327
+     * @return boolean|null
328
+     * @throws LoginException
329
+     */
330
+    public function login($uid, $password) {
331
+        $this->session->regenerateId();
332
+        if ($this->validateToken($password, $uid)) {
333
+            return $this->loginWithToken($password);
334
+        }
335
+        return $this->loginWithPassword($uid, $password);
336
+    }
337
+
338
+    /**
339
+     * @param IUser $user
340
+     * @param array $loginDetails
341
+     * @param bool $regenerateSessionId
342
+     * @return true returns true if login successful or an exception otherwise
343
+     * @throws LoginException
344
+     */
345
+    public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
346
+        if (!$user->isEnabled()) {
347
+            // disabled users can not log in
348
+            // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
349
+            $message = \OC::$server->getL10N('lib')->t('User disabled');
350
+            throw new LoginException($message);
351
+        }
352
+
353
+        if($regenerateSessionId) {
354
+            $this->session->regenerateId();
355
+        }
356
+
357
+        $this->setUser($user);
358
+        $this->setLoginName($loginDetails['loginName']);
359
+
360
+        if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) {
361
+            $this->setToken($loginDetails['token']->getId());
362
+            $this->lockdownManager->setToken($loginDetails['token']);
363
+            $firstTimeLogin = false;
364
+        } else {
365
+            $this->setToken(null);
366
+            $firstTimeLogin = $user->updateLastLoginTimestamp();
367
+        }
368
+        $this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]);
369
+        if($this->isLoggedIn()) {
370
+            $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
371
+            return true;
372
+        }
373
+
374
+        $message = \OC::$server->getL10N('lib')->t('Login canceled by app');
375
+        throw new LoginException($message);
376
+    }
377
+
378
+    /**
379
+     * Tries to log in a client
380
+     *
381
+     * Checks token auth enforced
382
+     * Checks 2FA enabled
383
+     *
384
+     * @param string $user
385
+     * @param string $password
386
+     * @param IRequest $request
387
+     * @param OC\Security\Bruteforce\Throttler $throttler
388
+     * @throws LoginException
389
+     * @throws PasswordLoginForbiddenException
390
+     * @return boolean
391
+     */
392
+    public function logClientIn($user,
393
+                                $password,
394
+                                IRequest $request,
395
+                                OC\Security\Bruteforce\Throttler $throttler) {
396
+        $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
397
+
398
+        if ($this->manager instanceof PublicEmitter) {
399
+            $this->manager->emit('\OC\User', 'preLogin', array($user, $password));
400
+        }
401
+
402
+        $isTokenPassword = $this->isTokenPassword($password);
403
+        if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
404
+            throw new PasswordLoginForbiddenException();
405
+        }
406
+        if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
407
+            throw new PasswordLoginForbiddenException();
408
+        }
409
+
410
+        // Try to login with this username and password
411
+        if (!$this->login($user, $password) ) {
412
+
413
+            // Failed, maybe the user used their email address
414
+            $users = $this->manager->getByEmail($user);
415
+            if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
416
+
417
+                $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
418
+
419
+                $throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]);
420
+                if ($currentDelay === 0) {
421
+                    $throttler->sleepDelay($request->getRemoteAddress(), 'login');
422
+                }
423
+                return false;
424
+            }
425
+        }
426
+
427
+        if ($isTokenPassword) {
428
+            $this->session->set('app_password', $password);
429
+        } else if($this->supportsCookies($request)) {
430
+            // Password login, but cookies supported -> create (browser) session token
431
+            $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
432
+        }
433
+
434
+        return true;
435
+    }
436
+
437
+    protected function supportsCookies(IRequest $request) {
438
+        if (!is_null($request->getCookie('cookie_test'))) {
439
+            return true;
440
+        }
441
+        setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
442
+        return false;
443
+    }
444
+
445
+    private function isTokenAuthEnforced() {
446
+        return $this->config->getSystemValue('token_auth_enforced', false);
447
+    }
448
+
449
+    protected function isTwoFactorEnforced($username) {
450
+        Util::emitHook(
451
+            '\OCA\Files_Sharing\API\Server2Server',
452
+            'preLoginNameUsedAsUserName',
453
+            array('uid' => &$username)
454
+        );
455
+        $user = $this->manager->get($username);
456
+        if (is_null($user)) {
457
+            $users = $this->manager->getByEmail($username);
458
+            if (empty($users)) {
459
+                return false;
460
+            }
461
+            if (count($users) !== 1) {
462
+                return true;
463
+            }
464
+            $user = $users[0];
465
+        }
466
+        // DI not possible due to cyclic dependencies :'-/
467
+        return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
468
+    }
469
+
470
+    /**
471
+     * Check if the given 'password' is actually a device token
472
+     *
473
+     * @param string $password
474
+     * @return boolean
475
+     */
476
+    public function isTokenPassword($password) {
477
+        try {
478
+            $this->tokenProvider->getToken($password);
479
+            return true;
480
+        } catch (InvalidTokenException $ex) {
481
+            return false;
482
+        }
483
+    }
484
+
485
+    protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
486
+        if ($refreshCsrfToken) {
487
+            // TODO: mock/inject/use non-static
488
+            // Refresh the token
489
+            \OC::$server->getCsrfTokenManager()->refreshToken();
490
+        }
491
+
492
+        //we need to pass the user name, which may differ from login name
493
+        $user = $this->getUser()->getUID();
494
+        OC_Util::setupFS($user);
495
+
496
+        if ($firstTimeLogin) {
497
+            // TODO: lock necessary?
498
+            //trigger creation of user home and /files folder
499
+            $userFolder = \OC::$server->getUserFolder($user);
500
+
501
+            try {
502
+                // copy skeleton
503
+                \OC_Util::copySkeleton($user, $userFolder);
504
+            } catch (NotPermittedException $ex) {
505
+                // read only uses
506
+            }
507
+
508
+            // trigger any other initialization
509
+            \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
510
+        }
511
+    }
512
+
513
+    /**
514
+     * Tries to login the user with HTTP Basic Authentication
515
+     *
516
+     * @todo do not allow basic auth if the user is 2FA enforced
517
+     * @param IRequest $request
518
+     * @param OC\Security\Bruteforce\Throttler $throttler
519
+     * @return boolean if the login was successful
520
+     */
521
+    public function tryBasicAuthLogin(IRequest $request,
522
+                                        OC\Security\Bruteforce\Throttler $throttler) {
523
+        if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
524
+            try {
525
+                if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
526
+                    /**
527
+                     * Add DAV authenticated. This should in an ideal world not be
528
+                     * necessary but the iOS App reads cookies from anywhere instead
529
+                     * only the DAV endpoint.
530
+                     * This makes sure that the cookies will be valid for the whole scope
531
+                     * @see https://github.com/owncloud/core/issues/22893
532
+                     */
533
+                    $this->session->set(
534
+                        Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
535
+                    );
536
+
537
+                    // Set the last-password-confirm session to make the sudo mode work
538
+                        $this->session->set('last-password-confirm', $this->timeFactory->getTime());
539
+
540
+                    return true;
541
+                }
542
+            } catch (PasswordLoginForbiddenException $ex) {
543
+                // Nothing to do
544
+            }
545
+        }
546
+        return false;
547
+    }
548
+
549
+    /**
550
+     * Log an user in via login name and password
551
+     *
552
+     * @param string $uid
553
+     * @param string $password
554
+     * @return boolean
555
+     * @throws LoginException if an app canceld the login process or the user is not enabled
556
+     */
557
+    private function loginWithPassword($uid, $password) {
558
+        $user = $this->manager->checkPasswordNoLogging($uid, $password);
559
+        if ($user === false) {
560
+            // Password check failed
561
+            return false;
562
+        }
563
+
564
+        return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
565
+    }
566
+
567
+    /**
568
+     * Log an user in with a given token (id)
569
+     *
570
+     * @param string $token
571
+     * @return boolean
572
+     * @throws LoginException if an app canceled the login process or the user is not enabled
573
+     */
574
+    private function loginWithToken($token) {
575
+        try {
576
+            $dbToken = $this->tokenProvider->getToken($token);
577
+        } catch (InvalidTokenException $ex) {
578
+            return false;
579
+        }
580
+        $uid = $dbToken->getUID();
581
+
582
+        // When logging in with token, the password must be decrypted first before passing to login hook
583
+        $password = '';
584
+        try {
585
+            $password = $this->tokenProvider->getPassword($dbToken, $token);
586
+        } catch (PasswordlessTokenException $ex) {
587
+            // Ignore and use empty string instead
588
+        }
589
+
590
+        $this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
591
+
592
+        $user = $this->manager->get($uid);
593
+        if (is_null($user)) {
594
+            // user does not exist
595
+            return false;
596
+        }
597
+
598
+        return $this->completeLogin(
599
+            $user,
600
+            [
601
+                'loginName' => $dbToken->getLoginName(),
602
+                'password' => $password,
603
+                'token' => $dbToken
604
+            ],
605
+            false);
606
+    }
607
+
608
+    /**
609
+     * Create a new session token for the given user credentials
610
+     *
611
+     * @param IRequest $request
612
+     * @param string $uid user UID
613
+     * @param string $loginName login name
614
+     * @param string $password
615
+     * @param int $remember
616
+     * @return boolean
617
+     */
618
+    public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
619
+        if (is_null($this->manager->get($uid))) {
620
+            // User does not exist
621
+            return false;
622
+        }
623
+        $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
624
+        try {
625
+            $sessionId = $this->session->getId();
626
+            $pwd = $this->getPassword($password);
627
+            $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
628
+            return true;
629
+        } catch (SessionNotAvailableException $ex) {
630
+            // This can happen with OCC, where a memory session is used
631
+            // if a memory session is used, we shouldn't create a session token anyway
632
+            return false;
633
+        }
634
+    }
635
+
636
+    /**
637
+     * Checks if the given password is a token.
638
+     * If yes, the password is extracted from the token.
639
+     * If no, the same password is returned.
640
+     *
641
+     * @param string $password either the login password or a device token
642
+     * @return string|null the password or null if none was set in the token
643
+     */
644
+    private function getPassword($password) {
645
+        if (is_null($password)) {
646
+            // This is surely no token ;-)
647
+            return null;
648
+        }
649
+        try {
650
+            $token = $this->tokenProvider->getToken($password);
651
+            try {
652
+                return $this->tokenProvider->getPassword($token, $password);
653
+            } catch (PasswordlessTokenException $ex) {
654
+                return null;
655
+            }
656
+        } catch (InvalidTokenException $ex) {
657
+            return $password;
658
+        }
659
+    }
660
+
661
+    /**
662
+     * @param IToken $dbToken
663
+     * @param string $token
664
+     * @return boolean
665
+     */
666
+    private function checkTokenCredentials(IToken $dbToken, $token) {
667
+        // Check whether login credentials are still valid and the user was not disabled
668
+        // This check is performed each 5 minutes
669
+        $lastCheck = $dbToken->getLastCheck() ? : 0;
670
+        $now = $this->timeFactory->getTime();
671
+        if ($lastCheck > ($now - 60 * 5)) {
672
+            // Checked performed recently, nothing to do now
673
+            return true;
674
+        }
675
+
676
+        try {
677
+            $pwd = $this->tokenProvider->getPassword($dbToken, $token);
678
+        } catch (InvalidTokenException $ex) {
679
+            // An invalid token password was used -> log user out
680
+            return false;
681
+        } catch (PasswordlessTokenException $ex) {
682
+            // Token has no password
683
+
684
+            if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
685
+                $this->tokenProvider->invalidateToken($token);
686
+                return false;
687
+            }
688
+
689
+            $dbToken->setLastCheck($now);
690
+            return true;
691
+        }
692
+
693
+        if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
694
+            || (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
695
+            $this->tokenProvider->invalidateToken($token);
696
+            // Password has changed or user was disabled -> log user out
697
+            return false;
698
+        }
699
+        $dbToken->setLastCheck($now);
700
+        return true;
701
+    }
702
+
703
+    /**
704
+     * Check if the given token exists and performs password/user-enabled checks
705
+     *
706
+     * Invalidates the token if checks fail
707
+     *
708
+     * @param string $token
709
+     * @param string $user login name
710
+     * @return boolean
711
+     */
712
+    private function validateToken($token, $user = null) {
713
+        try {
714
+            $dbToken = $this->tokenProvider->getToken($token);
715
+        } catch (InvalidTokenException $ex) {
716
+            return false;
717
+        }
718
+
719
+        // Check if login names match
720
+        if (!is_null($user) && $dbToken->getLoginName() !== $user) {
721
+            // TODO: this makes it imposssible to use different login names on browser and client
722
+            // e.g. login by e-mail '[email protected]' on browser for generating the token will not
723
+            //      allow to use the client token with the login name 'user'.
724
+            return false;
725
+        }
726
+
727
+        if (!$this->checkTokenCredentials($dbToken, $token)) {
728
+            return false;
729
+        }
730
+
731
+        $this->tokenProvider->updateTokenActivity($dbToken);
732
+
733
+        return true;
734
+    }
735
+
736
+    /**
737
+     * Tries to login the user with auth token header
738
+     *
739
+     * @param IRequest $request
740
+     * @todo check remember me cookie
741
+     * @return boolean
742
+     */
743
+    public function tryTokenLogin(IRequest $request) {
744
+        $authHeader = $request->getHeader('Authorization');
745
+        if (strpos($authHeader, 'Bearer ') === false) {
746
+            // No auth header, let's try session id
747
+            try {
748
+                $token = $this->session->getId();
749
+            } catch (SessionNotAvailableException $ex) {
750
+                return false;
751
+            }
752
+        } else {
753
+            $token = substr($authHeader, 7);
754
+        }
755
+
756
+        if (!$this->loginWithToken($token)) {
757
+            return false;
758
+        }
759
+        if(!$this->validateToken($token)) {
760
+            return false;
761
+        }
762
+        return true;
763
+    }
764
+
765
+    /**
766
+     * perform login using the magic cookie (remember login)
767
+     *
768
+     * @param string $uid the username
769
+     * @param string $currentToken
770
+     * @param string $oldSessionId
771
+     * @return bool
772
+     */
773
+    public function loginWithCookie($uid, $currentToken, $oldSessionId) {
774
+        $this->session->regenerateId();
775
+        $this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
776
+        $user = $this->manager->get($uid);
777
+        if (is_null($user)) {
778
+            // user does not exist
779
+            return false;
780
+        }
781
+
782
+        // get stored tokens
783
+        $tokens = $this->config->getUserKeys($uid, 'login_token');
784
+        // test cookies token against stored tokens
785
+        if (!in_array($currentToken, $tokens, true)) {
786
+            return false;
787
+        }
788
+        // replace successfully used token with a new one
789
+        $this->config->deleteUserValue($uid, 'login_token', $currentToken);
790
+        $newToken = $this->random->generate(32);
791
+        $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
792
+
793
+        try {
794
+            $sessionId = $this->session->getId();
795
+            $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
796
+        } catch (SessionNotAvailableException $ex) {
797
+            return false;
798
+        } catch (InvalidTokenException $ex) {
799
+            \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
800
+            return false;
801
+        }
802
+
803
+        $this->setMagicInCookie($user->getUID(), $newToken);
804
+        $token = $this->tokenProvider->getToken($sessionId);
805
+
806
+        //login
807
+        $this->setUser($user);
808
+        $this->setLoginName($token->getLoginName());
809
+        $this->setToken($token->getId());
810
+        $this->lockdownManager->setToken($token);
811
+        $user->updateLastLoginTimestamp();
812
+        $password = null;
813
+        try {
814
+            $password = $this->tokenProvider->getPassword($token, $sessionId);
815
+        } catch (PasswordlessTokenException $ex) {
816
+            // Ignore
817
+        }
818
+        $this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
819
+        return true;
820
+    }
821
+
822
+    /**
823
+     * @param IUser $user
824
+     */
825
+    public function createRememberMeToken(IUser $user) {
826
+        $token = $this->random->generate(32);
827
+        $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
828
+        $this->setMagicInCookie($user->getUID(), $token);
829
+    }
830
+
831
+    /**
832
+     * logout the user from the session
833
+     */
834
+    public function logout() {
835
+        $this->manager->emit('\OC\User', 'logout');
836
+        $user = $this->getUser();
837
+        if (!is_null($user)) {
838
+            try {
839
+                $this->tokenProvider->invalidateToken($this->session->getId());
840
+            } catch (SessionNotAvailableException $ex) {
841
+
842
+            }
843
+        }
844
+        $this->setUser(null);
845
+        $this->setLoginName(null);
846
+        $this->setToken(null);
847
+        $this->unsetMagicInCookie();
848
+        $this->session->clear();
849
+        $this->manager->emit('\OC\User', 'postLogout');
850
+    }
851
+
852
+    /**
853
+     * Set cookie value to use in next page load
854
+     *
855
+     * @param string $username username to be set
856
+     * @param string $token
857
+     */
858
+    public function setMagicInCookie($username, $token) {
859
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
860
+        $webRoot = \OC::$WEBROOT;
861
+        if ($webRoot === '') {
862
+            $webRoot = '/';
863
+        }
864
+
865
+        $expires = $this->timeFactory->getTime() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
866
+        setcookie('nc_username', $username, $expires, $webRoot, '', $secureCookie, true);
867
+        setcookie('nc_token', $token, $expires, $webRoot, '', $secureCookie, true);
868
+        try {
869
+            setcookie('nc_session_id', $this->session->getId(), $expires, $webRoot, '', $secureCookie, true);
870
+        } catch (SessionNotAvailableException $ex) {
871
+            // ignore
872
+        }
873
+    }
874
+
875
+    /**
876
+     * Remove cookie for "remember username"
877
+     */
878
+    public function unsetMagicInCookie() {
879
+        //TODO: DI for cookies and IRequest
880
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
881
+
882
+        unset($_COOKIE['nc_username']); //TODO: DI
883
+        unset($_COOKIE['nc_token']);
884
+        unset($_COOKIE['nc_session_id']);
885
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
886
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
887
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
888
+        // old cookies might be stored under /webroot/ instead of /webroot
889
+        // and Firefox doesn't like it!
890
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
891
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
892
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
893
+    }
894
+
895
+    /**
896
+     * Update password of the browser session token if there is one
897
+     *
898
+     * @param string $password
899
+     */
900
+    public function updateSessionTokenPassword($password) {
901
+        try {
902
+            $sessionId = $this->session->getId();
903
+            $token = $this->tokenProvider->getToken($sessionId);
904
+            $this->tokenProvider->setPassword($token, $sessionId, $password);
905
+        } catch (SessionNotAvailableException $ex) {
906
+            // Nothing to do
907
+        } catch (InvalidTokenException $ex) {
908
+            // Nothing to do
909
+        }
910
+    }
911 911
 
912 912
 
913 913
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -350,14 +350,14 @@  discard block
 block discarded – undo
350 350
 			throw new LoginException($message);
351 351
 		}
352 352
 
353
-		if($regenerateSessionId) {
353
+		if ($regenerateSessionId) {
354 354
 			$this->session->regenerateId();
355 355
 		}
356 356
 
357 357
 		$this->setUser($user);
358 358
 		$this->setLoginName($loginDetails['loginName']);
359 359
 
360
-		if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) {
360
+		if (isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) {
361 361
 			$this->setToken($loginDetails['token']->getId());
362 362
 			$this->lockdownManager->setToken($loginDetails['token']);
363 363
 			$firstTimeLogin = false;
@@ -366,7 +366,7 @@  discard block
 block discarded – undo
366 366
 			$firstTimeLogin = $user->updateLastLoginTimestamp();
367 367
 		}
368 368
 		$this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]);
369
-		if($this->isLoggedIn()) {
369
+		if ($this->isLoggedIn()) {
370 370
 			$this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
371 371
 			return true;
372 372
 		}
@@ -408,13 +408,13 @@  discard block
 block discarded – undo
408 408
 		}
409 409
 
410 410
 		// Try to login with this username and password
411
-		if (!$this->login($user, $password) ) {
411
+		if (!$this->login($user, $password)) {
412 412
 
413 413
 			// Failed, maybe the user used their email address
414 414
 			$users = $this->manager->getByEmail($user);
415 415
 			if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
416 416
 
417
-				$this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
417
+				$this->logger->warning('Login failed: \''.$user.'\' (Remote IP: \''.\OC::$server->getRequest()->getRemoteAddress().'\')', ['app' => 'core']);
418 418
 
419 419
 				$throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]);
420 420
 				if ($currentDelay === 0) {
@@ -426,7 +426,7 @@  discard block
 block discarded – undo
426 426
 
427 427
 		if ($isTokenPassword) {
428 428
 			$this->session->set('app_password', $password);
429
-		} else if($this->supportsCookies($request)) {
429
+		} else if ($this->supportsCookies($request)) {
430 430
 			// Password login, but cookies supported -> create (browser) session token
431 431
 			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
432 432
 		}
@@ -506,7 +506,7 @@  discard block
 block discarded – undo
506 506
 			}
507 507
 
508 508
 			// trigger any other initialization
509
-			\OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
509
+			\OC::$server->getEventDispatcher()->dispatch(IUser::class.'::firstLogin', new GenericEvent($this->getUser()));
510 510
 		}
511 511
 	}
512 512
 
@@ -666,7 +666,7 @@  discard block
 block discarded – undo
666 666
 	private function checkTokenCredentials(IToken $dbToken, $token) {
667 667
 		// Check whether login credentials are still valid and the user was not disabled
668 668
 		// This check is performed each 5 minutes
669
-		$lastCheck = $dbToken->getLastCheck() ? : 0;
669
+		$lastCheck = $dbToken->getLastCheck() ?: 0;
670 670
 		$now = $this->timeFactory->getTime();
671 671
 		if ($lastCheck > ($now - 60 * 5)) {
672 672
 			// Checked performed recently, nothing to do now
@@ -756,7 +756,7 @@  discard block
 block discarded – undo
756 756
 		if (!$this->loginWithToken($token)) {
757 757
 			return false;
758 758
 		}
759
-		if(!$this->validateToken($token)) {
759
+		if (!$this->validateToken($token)) {
760 760
 			return false;
761 761
 		}
762 762
 		return true;
@@ -887,9 +887,9 @@  discard block
 block discarded – undo
887 887
 		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
888 888
 		// old cookies might be stored under /webroot/ instead of /webroot
889 889
 		// and Firefox doesn't like it!
890
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
891
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
892
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
890
+		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT.'/', '', $secureCookie, true);
891
+		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT.'/', '', $secureCookie, true);
892
+		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT.'/', '', $secureCookie, true);
893 893
 	}
894 894
 
895 895
 	/**
Please login to merge, or discard this patch.