Passed
Push — master ( a5c558...5094e2 )
by Julius
13:28 queued 12s
created
lib/private/Files/Storage/Wrapper/Encryption.php 1 patch
Indentation   +988 added lines, -988 removed lines patch added patch discarded remove patch
@@ -51,992 +51,992 @@
 block discarded – undo
51 51
 use OCP\ILogger;
52 52
 
53 53
 class Encryption extends Wrapper {
54
-	use LocalTempFileTrait;
55
-
56
-	/** @var string */
57
-	private $mountPoint;
58
-
59
-	/** @var \OC\Encryption\Util */
60
-	private $util;
61
-
62
-	/** @var \OCP\Encryption\IManager */
63
-	private $encryptionManager;
64
-
65
-	/** @var \OCP\ILogger */
66
-	private $logger;
67
-
68
-	/** @var string */
69
-	private $uid;
70
-
71
-	/** @var array */
72
-	protected $unencryptedSize;
73
-
74
-	/** @var \OCP\Encryption\IFile */
75
-	private $fileHelper;
76
-
77
-	/** @var IMountPoint */
78
-	private $mount;
79
-
80
-	/** @var IStorage */
81
-	private $keyStorage;
82
-
83
-	/** @var Update */
84
-	private $update;
85
-
86
-	/** @var Manager */
87
-	private $mountManager;
88
-
89
-	/** @var array remember for which path we execute the repair step to avoid recursions */
90
-	private $fixUnencryptedSizeOf = [];
91
-
92
-	/** @var  ArrayCache */
93
-	private $arrayCache;
94
-
95
-	/**
96
-	 * @param array $parameters
97
-	 * @param IManager $encryptionManager
98
-	 * @param Util $util
99
-	 * @param ILogger $logger
100
-	 * @param IFile $fileHelper
101
-	 * @param string $uid
102
-	 * @param IStorage $keyStorage
103
-	 * @param Update $update
104
-	 * @param Manager $mountManager
105
-	 * @param ArrayCache $arrayCache
106
-	 */
107
-	public function __construct(
108
-		$parameters,
109
-		IManager $encryptionManager = null,
110
-		Util $util = null,
111
-		ILogger $logger = null,
112
-		IFile $fileHelper = null,
113
-		$uid = null,
114
-		IStorage $keyStorage = null,
115
-		Update $update = null,
116
-		Manager $mountManager = null,
117
-		ArrayCache $arrayCache = null
118
-	) {
119
-		$this->mountPoint = $parameters['mountPoint'];
120
-		$this->mount = $parameters['mount'];
121
-		$this->encryptionManager = $encryptionManager;
122
-		$this->util = $util;
123
-		$this->logger = $logger;
124
-		$this->uid = $uid;
125
-		$this->fileHelper = $fileHelper;
126
-		$this->keyStorage = $keyStorage;
127
-		$this->unencryptedSize = [];
128
-		$this->update = $update;
129
-		$this->mountManager = $mountManager;
130
-		$this->arrayCache = $arrayCache;
131
-		parent::__construct($parameters);
132
-	}
133
-
134
-	/**
135
-	 * see http://php.net/manual/en/function.filesize.php
136
-	 * The result for filesize when called on a folder is required to be 0
137
-	 *
138
-	 * @param string $path
139
-	 * @return int
140
-	 */
141
-	public function filesize($path) {
142
-		$fullPath = $this->getFullPath($path);
143
-
144
-		/** @var CacheEntry $info */
145
-		$info = $this->getCache()->get($path);
146
-		if (isset($this->unencryptedSize[$fullPath])) {
147
-			$size = $this->unencryptedSize[$fullPath];
148
-			// update file cache
149
-			if ($info instanceof ICacheEntry) {
150
-				$info = $info->getData();
151
-				$info['encrypted'] = $info['encryptedVersion'];
152
-			} else {
153
-				if (!is_array($info)) {
154
-					$info = [];
155
-				}
156
-				$info['encrypted'] = true;
157
-			}
158
-
159
-			$info['size'] = $size;
160
-			$this->getCache()->put($path, $info);
161
-
162
-			return $size;
163
-		}
164
-
165
-		if (isset($info['fileid']) && $info['encrypted']) {
166
-			return $this->verifyUnencryptedSize($path, $info['size']);
167
-		}
168
-
169
-		return $this->storage->filesize($path);
170
-	}
171
-
172
-	private function modifyMetaData(string $path, array $data): array {
173
-		$fullPath = $this->getFullPath($path);
174
-		$info = $this->getCache()->get($path);
175
-
176
-		if (isset($this->unencryptedSize[$fullPath])) {
177
-			$data['encrypted'] = true;
178
-			$data['size'] = $this->unencryptedSize[$fullPath];
179
-		} else {
180
-			if (isset($info['fileid']) && $info['encrypted']) {
181
-				$data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
182
-				$data['encrypted'] = true;
183
-			}
184
-		}
185
-
186
-		if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
187
-			$data['encryptedVersion'] = $info['encryptedVersion'];
188
-		}
189
-
190
-		return $data;
191
-	}
192
-
193
-	/**
194
-	 * @param string $path
195
-	 * @return array
196
-	 */
197
-	public function getMetaData($path) {
198
-		$data = $this->storage->getMetaData($path);
199
-		if (is_null($data)) {
200
-			return null;
201
-		}
202
-		return $this->modifyMetaData($path, $data);
203
-	}
204
-
205
-	public function getDirectoryContent($directory): \Traversable {
206
-		$parent = rtrim($directory, '/');
207
-		foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
208
-			yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
209
-		}
210
-	}
211
-
212
-	/**
213
-	 * see http://php.net/manual/en/function.file_get_contents.php
214
-	 *
215
-	 * @param string $path
216
-	 * @return string
217
-	 */
218
-	public function file_get_contents($path) {
219
-		$encryptionModule = $this->getEncryptionModule($path);
220
-
221
-		if ($encryptionModule) {
222
-			$handle = $this->fopen($path, "r");
223
-			if (!$handle) {
224
-				return false;
225
-			}
226
-			$data = stream_get_contents($handle);
227
-			fclose($handle);
228
-			return $data;
229
-		}
230
-		return $this->storage->file_get_contents($path);
231
-	}
232
-
233
-	/**
234
-	 * see http://php.net/manual/en/function.file_put_contents.php
235
-	 *
236
-	 * @param string $path
237
-	 * @param mixed $data
238
-	 * @return int|false
239
-	 */
240
-	public function file_put_contents($path, $data) {
241
-		// file put content will always be translated to a stream write
242
-		$handle = $this->fopen($path, 'w');
243
-		if (is_resource($handle)) {
244
-			$written = fwrite($handle, $data);
245
-			fclose($handle);
246
-			return $written;
247
-		}
248
-
249
-		return false;
250
-	}
251
-
252
-	/**
253
-	 * see http://php.net/manual/en/function.unlink.php
254
-	 *
255
-	 * @param string $path
256
-	 * @return bool
257
-	 */
258
-	public function unlink($path) {
259
-		$fullPath = $this->getFullPath($path);
260
-		if ($this->util->isExcluded($fullPath)) {
261
-			return $this->storage->unlink($path);
262
-		}
263
-
264
-		$encryptionModule = $this->getEncryptionModule($path);
265
-		if ($encryptionModule) {
266
-			$this->keyStorage->deleteAllFileKeys($fullPath);
267
-		}
268
-
269
-		return $this->storage->unlink($path);
270
-	}
271
-
272
-	/**
273
-	 * see http://php.net/manual/en/function.rename.php
274
-	 *
275
-	 * @param string $path1
276
-	 * @param string $path2
277
-	 * @return bool
278
-	 */
279
-	public function rename($path1, $path2) {
280
-		$result = $this->storage->rename($path1, $path2);
281
-
282
-		if ($result &&
283
-			// versions always use the keys from the original file, so we can skip
284
-			// this step for versions
285
-			$this->isVersion($path2) === false &&
286
-			$this->encryptionManager->isEnabled()) {
287
-			$source = $this->getFullPath($path1);
288
-			if (!$this->util->isExcluded($source)) {
289
-				$target = $this->getFullPath($path2);
290
-				if (isset($this->unencryptedSize[$source])) {
291
-					$this->unencryptedSize[$target] = $this->unencryptedSize[$source];
292
-				}
293
-				$this->keyStorage->renameKeys($source, $target);
294
-				$module = $this->getEncryptionModule($path2);
295
-				if ($module) {
296
-					$module->update($target, $this->uid, []);
297
-				}
298
-			}
299
-		}
300
-
301
-		return $result;
302
-	}
303
-
304
-	/**
305
-	 * see http://php.net/manual/en/function.rmdir.php
306
-	 *
307
-	 * @param string $path
308
-	 * @return bool
309
-	 */
310
-	public function rmdir($path) {
311
-		$result = $this->storage->rmdir($path);
312
-		$fullPath = $this->getFullPath($path);
313
-		if ($result &&
314
-			$this->util->isExcluded($fullPath) === false &&
315
-			$this->encryptionManager->isEnabled()
316
-		) {
317
-			$this->keyStorage->deleteAllFileKeys($fullPath);
318
-		}
319
-
320
-		return $result;
321
-	}
322
-
323
-	/**
324
-	 * check if a file can be read
325
-	 *
326
-	 * @param string $path
327
-	 * @return bool
328
-	 */
329
-	public function isReadable($path) {
330
-		$isReadable = true;
331
-
332
-		$metaData = $this->getMetaData($path);
333
-		if (
334
-			!$this->is_dir($path) &&
335
-			isset($metaData['encrypted']) &&
336
-			$metaData['encrypted'] === true
337
-		) {
338
-			$fullPath = $this->getFullPath($path);
339
-			$module = $this->getEncryptionModule($path);
340
-			$isReadable = $module->isReadable($fullPath, $this->uid);
341
-		}
342
-
343
-		return $this->storage->isReadable($path) && $isReadable;
344
-	}
345
-
346
-	/**
347
-	 * see http://php.net/manual/en/function.copy.php
348
-	 *
349
-	 * @param string $path1
350
-	 * @param string $path2
351
-	 * @return bool
352
-	 */
353
-	public function copy($path1, $path2) {
354
-		$source = $this->getFullPath($path1);
355
-
356
-		if ($this->util->isExcluded($source)) {
357
-			return $this->storage->copy($path1, $path2);
358
-		}
359
-
360
-		// need to stream copy file by file in case we copy between a encrypted
361
-		// and a unencrypted storage
362
-		$this->unlink($path2);
363
-		return $this->copyFromStorage($this, $path1, $path2);
364
-	}
365
-
366
-	/**
367
-	 * see http://php.net/manual/en/function.fopen.php
368
-	 *
369
-	 * @param string $path
370
-	 * @param string $mode
371
-	 * @return resource|bool
372
-	 * @throws GenericEncryptionException
373
-	 * @throws ModuleDoesNotExistsException
374
-	 */
375
-	public function fopen($path, $mode) {
376
-
377
-		// check if the file is stored in the array cache, this means that we
378
-		// copy a file over to the versions folder, in this case we don't want to
379
-		// decrypt it
380
-		if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
381
-			$this->arrayCache->remove('encryption_copy_version_' . $path);
382
-			return $this->storage->fopen($path, $mode);
383
-		}
384
-
385
-		$encryptionEnabled = $this->encryptionManager->isEnabled();
386
-		$shouldEncrypt = false;
387
-		$encryptionModule = null;
388
-		$header = $this->getHeader($path);
389
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
390
-		$fullPath = $this->getFullPath($path);
391
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
392
-
393
-		if ($this->util->isExcluded($fullPath) === false) {
394
-			$size = $unencryptedSize = 0;
395
-			$realFile = $this->util->stripPartialFileExtension($path);
396
-			$targetExists = $this->file_exists($realFile) || $this->file_exists($path);
397
-			$targetIsEncrypted = false;
398
-			if ($targetExists) {
399
-				// in case the file exists we require the explicit module as
400
-				// specified in the file header - otherwise we need to fail hard to
401
-				// prevent data loss on client side
402
-				if (!empty($encryptionModuleId)) {
403
-					$targetIsEncrypted = true;
404
-					$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
405
-				}
406
-
407
-				if ($this->file_exists($path)) {
408
-					$size = $this->storage->filesize($path);
409
-					$unencryptedSize = $this->filesize($path);
410
-				} else {
411
-					$size = $unencryptedSize = 0;
412
-				}
413
-			}
414
-
415
-			try {
416
-				if (
417
-					$mode === 'w'
418
-					|| $mode === 'w+'
419
-					|| $mode === 'wb'
420
-					|| $mode === 'wb+'
421
-				) {
422
-					// if we update a encrypted file with a un-encrypted one we change the db flag
423
-					if ($targetIsEncrypted && $encryptionEnabled === false) {
424
-						$cache = $this->storage->getCache();
425
-						if ($cache) {
426
-							$entry = $cache->get($path);
427
-							$cache->update($entry->getId(), ['encrypted' => 0]);
428
-						}
429
-					}
430
-					if ($encryptionEnabled) {
431
-						// if $encryptionModuleId is empty, the default module will be used
432
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
433
-						$shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
434
-						$signed = true;
435
-					}
436
-				} else {
437
-					$info = $this->getCache()->get($path);
438
-					// only get encryption module if we found one in the header
439
-					// or if file should be encrypted according to the file cache
440
-					if (!empty($encryptionModuleId)) {
441
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
442
-						$shouldEncrypt = true;
443
-					} elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
444
-						// we come from a old installation. No header and/or no module defined
445
-						// but the file is encrypted. In this case we need to use the
446
-						// OC_DEFAULT_MODULE to read the file
447
-						$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
448
-						$shouldEncrypt = true;
449
-						$targetIsEncrypted = true;
450
-					}
451
-				}
452
-			} catch (ModuleDoesNotExistsException $e) {
453
-				$this->logger->logException($e, [
454
-					'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
455
-					'level' => ILogger::WARN,
456
-					'app' => 'core',
457
-				]);
458
-			}
459
-
460
-			// encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
461
-			if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
462
-				if (!$targetExists || !$targetIsEncrypted) {
463
-					$shouldEncrypt = false;
464
-				}
465
-			}
466
-
467
-			if ($shouldEncrypt === true && $encryptionModule !== null) {
468
-				$headerSize = $this->getHeaderSize($path);
469
-				$source = $this->storage->fopen($path, $mode);
470
-				if (!is_resource($source)) {
471
-					return false;
472
-				}
473
-				$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
474
-					$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
475
-					$size, $unencryptedSize, $headerSize, $signed);
476
-				return $handle;
477
-			}
478
-		}
479
-
480
-		return $this->storage->fopen($path, $mode);
481
-	}
482
-
483
-
484
-	/**
485
-	 * perform some plausibility checks if the the unencrypted size is correct.
486
-	 * If not, we calculate the correct unencrypted size and return it
487
-	 *
488
-	 * @param string $path internal path relative to the storage root
489
-	 * @param int $unencryptedSize size of the unencrypted file
490
-	 *
491
-	 * @return int unencrypted size
492
-	 */
493
-	protected function verifyUnencryptedSize($path, $unencryptedSize) {
494
-		$size = $this->storage->filesize($path);
495
-		$result = $unencryptedSize;
496
-
497
-		if ($unencryptedSize < 0 ||
498
-			($size > 0 && $unencryptedSize === $size)
499
-		) {
500
-			// check if we already calculate the unencrypted size for the
501
-			// given path to avoid recursions
502
-			if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
503
-				$this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
504
-				try {
505
-					$result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
506
-				} catch (\Exception $e) {
507
-					$this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path);
508
-					$this->logger->logException($e);
509
-				}
510
-				unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
511
-			}
512
-		}
513
-
514
-		return $result;
515
-	}
516
-
517
-	/**
518
-	 * calculate the unencrypted size
519
-	 *
520
-	 * @param string $path internal path relative to the storage root
521
-	 * @param int $size size of the physical file
522
-	 * @param int $unencryptedSize size of the unencrypted file
523
-	 *
524
-	 * @return int calculated unencrypted size
525
-	 */
526
-	protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
527
-		$headerSize = $this->getHeaderSize($path);
528
-		$header = $this->getHeader($path);
529
-		$encryptionModule = $this->getEncryptionModule($path);
530
-
531
-		$stream = $this->storage->fopen($path, 'r');
532
-
533
-		// if we couldn't open the file we return the old unencrypted size
534
-		if (!is_resource($stream)) {
535
-			$this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
536
-			return $unencryptedSize;
537
-		}
538
-
539
-		$newUnencryptedSize = 0;
540
-		$size -= $headerSize;
541
-		$blockSize = $this->util->getBlockSize();
542
-
543
-		// if a header exists we skip it
544
-		if ($headerSize > 0) {
545
-			fread($stream, $headerSize);
546
-		}
547
-
548
-		// fast path, else the calculation for $lastChunkNr is bogus
549
-		if ($size === 0) {
550
-			return 0;
551
-		}
552
-
553
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
554
-		$unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
555
-
556
-		// calculate last chunk nr
557
-		// next highest is end of chunks, one subtracted is last one
558
-		// we have to read the last chunk, we can't just calculate it (because of padding etc)
559
-
560
-		$lastChunkNr = ceil($size / $blockSize) - 1;
561
-		// calculate last chunk position
562
-		$lastChunkPos = ($lastChunkNr * $blockSize);
563
-		// try to fseek to the last chunk, if it fails we have to read the whole file
564
-		if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
565
-			$newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
566
-		}
567
-
568
-		$lastChunkContentEncrypted = '';
569
-		$count = $blockSize;
570
-
571
-		while ($count > 0) {
572
-			$data = fread($stream, $blockSize);
573
-			$count = strlen($data);
574
-			$lastChunkContentEncrypted .= $data;
575
-			if (strlen($lastChunkContentEncrypted) > $blockSize) {
576
-				$newUnencryptedSize += $unencryptedBlockSize;
577
-				$lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize);
578
-			}
579
-		}
580
-
581
-		fclose($stream);
582
-
583
-		// we have to decrypt the last chunk to get it actual size
584
-		$encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
585
-		$decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
586
-		$decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
587
-
588
-		// calc the real file size with the size of the last chunk
589
-		$newUnencryptedSize += strlen($decryptedLastChunk);
590
-
591
-		$this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
592
-
593
-		// write to cache if applicable
594
-		$cache = $this->storage->getCache();
595
-		if ($cache) {
596
-			$entry = $cache->get($path);
597
-			$cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
598
-		}
599
-
600
-		return $newUnencryptedSize;
601
-	}
602
-
603
-	/**
604
-	 * @param Storage\IStorage $sourceStorage
605
-	 * @param string $sourceInternalPath
606
-	 * @param string $targetInternalPath
607
-	 * @param bool $preserveMtime
608
-	 * @return bool
609
-	 */
610
-	public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
611
-		if ($sourceStorage === $this) {
612
-			return $this->rename($sourceInternalPath, $targetInternalPath);
613
-		}
614
-
615
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
616
-		// - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
617
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
618
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
619
-		// - remove $this->copyBetweenStorage
620
-
621
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
622
-			return false;
623
-		}
624
-
625
-		$result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
626
-		if ($result) {
627
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
628
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
629
-			} else {
630
-				$result &= $sourceStorage->unlink($sourceInternalPath);
631
-			}
632
-		}
633
-		return $result;
634
-	}
635
-
636
-
637
-	/**
638
-	 * @param Storage\IStorage $sourceStorage
639
-	 * @param string $sourceInternalPath
640
-	 * @param string $targetInternalPath
641
-	 * @param bool $preserveMtime
642
-	 * @param bool $isRename
643
-	 * @return bool
644
-	 */
645
-	public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
646
-
647
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
648
-		// - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
649
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
650
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
651
-		// - remove $this->copyBetweenStorage
652
-
653
-		return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
654
-	}
655
-
656
-	/**
657
-	 * Update the encrypted cache version in the database
658
-	 *
659
-	 * @param Storage\IStorage $sourceStorage
660
-	 * @param string $sourceInternalPath
661
-	 * @param string $targetInternalPath
662
-	 * @param bool $isRename
663
-	 * @param bool $keepEncryptionVersion
664
-	 */
665
-	private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
666
-		$isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
667
-		$cacheInformation = [
668
-			'encrypted' => $isEncrypted,
669
-		];
670
-		if ($isEncrypted) {
671
-			$encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
672
-
673
-			// In case of a move operation from an unencrypted to an encrypted
674
-			// storage the old encrypted version would stay with "0" while the
675
-			// correct value would be "1". Thus we manually set the value to "1"
676
-			// for those cases.
677
-			// See also https://github.com/owncloud/core/issues/23078
678
-			if ($encryptedVersion === 0 || !$keepEncryptionVersion) {
679
-				$encryptedVersion = 1;
680
-			}
681
-
682
-			$cacheInformation['encryptedVersion'] = $encryptedVersion;
683
-		}
684
-
685
-		// in case of a rename we need to manipulate the source cache because
686
-		// this information will be kept for the new target
687
-		if ($isRename) {
688
-			$sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
689
-		} else {
690
-			$this->getCache()->put($targetInternalPath, $cacheInformation);
691
-		}
692
-	}
693
-
694
-	/**
695
-	 * copy file between two storages
696
-	 *
697
-	 * @param Storage\IStorage $sourceStorage
698
-	 * @param string $sourceInternalPath
699
-	 * @param string $targetInternalPath
700
-	 * @param bool $preserveMtime
701
-	 * @param bool $isRename
702
-	 * @return bool
703
-	 * @throws \Exception
704
-	 */
705
-	private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
706
-
707
-		// for versions we have nothing to do, because versions should always use the
708
-		// key from the original file. Just create a 1:1 copy and done
709
-		if ($this->isVersion($targetInternalPath) ||
710
-			$this->isVersion($sourceInternalPath)) {
711
-			// remember that we try to create a version so that we can detect it during
712
-			// fopen($sourceInternalPath) and by-pass the encryption in order to
713
-			// create a 1:1 copy of the file
714
-			$this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
715
-			$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
716
-			$this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
717
-			if ($result) {
718
-				$info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
719
-				// make sure that we update the unencrypted size for the version
720
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
721
-					$this->updateUnencryptedSize(
722
-						$this->getFullPath($targetInternalPath),
723
-						$info['size']
724
-					);
725
-				}
726
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
727
-			}
728
-			return $result;
729
-		}
730
-
731
-		// first copy the keys that we reuse the existing file key on the target location
732
-		// and don't create a new one which would break versions for example.
733
-		$mount = $this->mountManager->findByStorageId($sourceStorage->getId());
734
-		if (count($mount) === 1) {
735
-			$mountPoint = $mount[0]->getMountPoint();
736
-			$source = $mountPoint . '/' . $sourceInternalPath;
737
-			$target = $this->getFullPath($targetInternalPath);
738
-			$this->copyKeys($source, $target);
739
-		} else {
740
-			$this->logger->error('Could not find mount point, can\'t keep encryption keys');
741
-		}
742
-
743
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
744
-			$dh = $sourceStorage->opendir($sourceInternalPath);
745
-			$result = $this->mkdir($targetInternalPath);
746
-			if (is_resource($dh)) {
747
-				while ($result and ($file = readdir($dh)) !== false) {
748
-					if (!Filesystem::isIgnoredDir($file)) {
749
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
750
-					}
751
-				}
752
-			}
753
-		} else {
754
-			try {
755
-				$source = $sourceStorage->fopen($sourceInternalPath, 'r');
756
-				$target = $this->fopen($targetInternalPath, 'w');
757
-				[, $result] = \OC_Helper::streamCopy($source, $target);
758
-				fclose($source);
759
-				fclose($target);
760
-			} catch (\Exception $e) {
761
-				fclose($source);
762
-				fclose($target);
763
-				throw $e;
764
-			}
765
-			if ($result) {
766
-				if ($preserveMtime) {
767
-					$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
768
-				}
769
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
770
-			} else {
771
-				// delete partially written target file
772
-				$this->unlink($targetInternalPath);
773
-				// delete cache entry that was created by fopen
774
-				$this->getCache()->remove($targetInternalPath);
775
-			}
776
-		}
777
-		return (bool)$result;
778
-	}
779
-
780
-	/**
781
-	 * get the path to a local version of the file.
782
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
783
-	 *
784
-	 * @param string $path
785
-	 * @return string
786
-	 */
787
-	public function getLocalFile($path) {
788
-		if ($this->encryptionManager->isEnabled()) {
789
-			$cachedFile = $this->getCachedFile($path);
790
-			if (is_string($cachedFile)) {
791
-				return $cachedFile;
792
-			}
793
-		}
794
-		return $this->storage->getLocalFile($path);
795
-	}
796
-
797
-	/**
798
-	 * Returns the wrapped storage's value for isLocal()
799
-	 *
800
-	 * @return bool wrapped storage's isLocal() value
801
-	 */
802
-	public function isLocal() {
803
-		if ($this->encryptionManager->isEnabled()) {
804
-			return false;
805
-		}
806
-		return $this->storage->isLocal();
807
-	}
808
-
809
-	/**
810
-	 * see http://php.net/manual/en/function.stat.php
811
-	 * only the following keys are required in the result: size and mtime
812
-	 *
813
-	 * @param string $path
814
-	 * @return array
815
-	 */
816
-	public function stat($path) {
817
-		$stat = $this->storage->stat($path);
818
-		$fileSize = $this->filesize($path);
819
-		$stat['size'] = $fileSize;
820
-		$stat[7] = $fileSize;
821
-		$stat['hasHeader'] = $this->getHeaderSize($path) > 0;
822
-		return $stat;
823
-	}
824
-
825
-	/**
826
-	 * see http://php.net/manual/en/function.hash.php
827
-	 *
828
-	 * @param string $type
829
-	 * @param string $path
830
-	 * @param bool $raw
831
-	 * @return string
832
-	 */
833
-	public function hash($type, $path, $raw = false) {
834
-		$fh = $this->fopen($path, 'rb');
835
-		$ctx = hash_init($type);
836
-		hash_update_stream($ctx, $fh);
837
-		fclose($fh);
838
-		return hash_final($ctx, $raw);
839
-	}
840
-
841
-	/**
842
-	 * return full path, including mount point
843
-	 *
844
-	 * @param string $path relative to mount point
845
-	 * @return string full path including mount point
846
-	 */
847
-	protected function getFullPath($path) {
848
-		return Filesystem::normalizePath($this->mountPoint . '/' . $path);
849
-	}
850
-
851
-	/**
852
-	 * read first block of encrypted file, typically this will contain the
853
-	 * encryption header
854
-	 *
855
-	 * @param string $path
856
-	 * @return string
857
-	 */
858
-	protected function readFirstBlock($path) {
859
-		$firstBlock = '';
860
-		if ($this->storage->file_exists($path)) {
861
-			$handle = $this->storage->fopen($path, 'r');
862
-			$firstBlock = fread($handle, $this->util->getHeaderSize());
863
-			fclose($handle);
864
-		}
865
-		return $firstBlock;
866
-	}
867
-
868
-	/**
869
-	 * return header size of given file
870
-	 *
871
-	 * @param string $path
872
-	 * @return int
873
-	 */
874
-	protected function getHeaderSize($path) {
875
-		$headerSize = 0;
876
-		$realFile = $this->util->stripPartialFileExtension($path);
877
-		if ($this->storage->file_exists($realFile)) {
878
-			$path = $realFile;
879
-		}
880
-		$firstBlock = $this->readFirstBlock($path);
881
-
882
-		if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
883
-			$headerSize = $this->util->getHeaderSize();
884
-		}
885
-
886
-		return $headerSize;
887
-	}
888
-
889
-	/**
890
-	 * parse raw header to array
891
-	 *
892
-	 * @param string $rawHeader
893
-	 * @return array
894
-	 */
895
-	protected function parseRawHeader($rawHeader) {
896
-		$result = [];
897
-		if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
898
-			$header = $rawHeader;
899
-			$endAt = strpos($header, Util::HEADER_END);
900
-			if ($endAt !== false) {
901
-				$header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
902
-
903
-				// +1 to not start with an ':' which would result in empty element at the beginning
904
-				$exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
905
-
906
-				$element = array_shift($exploded);
907
-				while ($element !== Util::HEADER_END) {
908
-					$result[$element] = array_shift($exploded);
909
-					$element = array_shift($exploded);
910
-				}
911
-			}
912
-		}
913
-
914
-		return $result;
915
-	}
916
-
917
-	/**
918
-	 * read header from file
919
-	 *
920
-	 * @param string $path
921
-	 * @return array
922
-	 */
923
-	protected function getHeader($path) {
924
-		$realFile = $this->util->stripPartialFileExtension($path);
925
-		$exists = $this->storage->file_exists($realFile);
926
-		if ($exists) {
927
-			$path = $realFile;
928
-		}
929
-
930
-		$firstBlock = $this->readFirstBlock($path);
931
-		$result = $this->parseRawHeader($firstBlock);
932
-
933
-		// if the header doesn't contain a encryption module we check if it is a
934
-		// legacy file. If true, we add the default encryption module
935
-		if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
936
-			if (!empty($result)) {
937
-				$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
938
-			} elseif ($exists) {
939
-				// if the header was empty we have to check first if it is a encrypted file at all
940
-				// We would do query to filecache only if we know that entry in filecache exists
941
-				$info = $this->getCache()->get($path);
942
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
943
-					$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
944
-				}
945
-			}
946
-		}
947
-
948
-		return $result;
949
-	}
950
-
951
-	/**
952
-	 * read encryption module needed to read/write the file located at $path
953
-	 *
954
-	 * @param string $path
955
-	 * @return null|\OCP\Encryption\IEncryptionModule
956
-	 * @throws ModuleDoesNotExistsException
957
-	 * @throws \Exception
958
-	 */
959
-	protected function getEncryptionModule($path) {
960
-		$encryptionModule = null;
961
-		$header = $this->getHeader($path);
962
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
963
-		if (!empty($encryptionModuleId)) {
964
-			try {
965
-				$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
966
-			} catch (ModuleDoesNotExistsException $e) {
967
-				$this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
968
-				throw $e;
969
-			}
970
-		}
971
-
972
-		return $encryptionModule;
973
-	}
974
-
975
-	/**
976
-	 * @param string $path
977
-	 * @param int $unencryptedSize
978
-	 */
979
-	public function updateUnencryptedSize($path, $unencryptedSize) {
980
-		$this->unencryptedSize[$path] = $unencryptedSize;
981
-	}
982
-
983
-	/**
984
-	 * copy keys to new location
985
-	 *
986
-	 * @param string $source path relative to data/
987
-	 * @param string $target path relative to data/
988
-	 * @return bool
989
-	 */
990
-	protected function copyKeys($source, $target) {
991
-		if (!$this->util->isExcluded($source)) {
992
-			return $this->keyStorage->copyKeys($source, $target);
993
-		}
994
-
995
-		return false;
996
-	}
997
-
998
-	/**
999
-	 * check if path points to a files version
1000
-	 *
1001
-	 * @param $path
1002
-	 * @return bool
1003
-	 */
1004
-	protected function isVersion($path) {
1005
-		$normalized = Filesystem::normalizePath($path);
1006
-		return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1007
-	}
1008
-
1009
-	/**
1010
-	 * check if the given storage should be encrypted or not
1011
-	 *
1012
-	 * @param $path
1013
-	 * @return bool
1014
-	 */
1015
-	protected function shouldEncrypt($path) {
1016
-		$fullPath = $this->getFullPath($path);
1017
-		$mountPointConfig = $this->mount->getOption('encrypt', true);
1018
-		if ($mountPointConfig === false) {
1019
-			return false;
1020
-		}
1021
-
1022
-		try {
1023
-			$encryptionModule = $this->getEncryptionModule($fullPath);
1024
-		} catch (ModuleDoesNotExistsException $e) {
1025
-			return false;
1026
-		}
1027
-
1028
-		if ($encryptionModule === null) {
1029
-			$encryptionModule = $this->encryptionManager->getEncryptionModule();
1030
-		}
1031
-
1032
-		return $encryptionModule->shouldEncrypt($fullPath);
1033
-	}
1034
-
1035
-	public function writeStream(string $path, $stream, int $size = null): int {
1036
-		// always fall back to fopen
1037
-		$target = $this->fopen($path, 'w');
1038
-		[$count, $result] = \OC_Helper::streamCopy($stream, $target);
1039
-		fclose($target);
1040
-		return $count;
1041
-	}
54
+    use LocalTempFileTrait;
55
+
56
+    /** @var string */
57
+    private $mountPoint;
58
+
59
+    /** @var \OC\Encryption\Util */
60
+    private $util;
61
+
62
+    /** @var \OCP\Encryption\IManager */
63
+    private $encryptionManager;
64
+
65
+    /** @var \OCP\ILogger */
66
+    private $logger;
67
+
68
+    /** @var string */
69
+    private $uid;
70
+
71
+    /** @var array */
72
+    protected $unencryptedSize;
73
+
74
+    /** @var \OCP\Encryption\IFile */
75
+    private $fileHelper;
76
+
77
+    /** @var IMountPoint */
78
+    private $mount;
79
+
80
+    /** @var IStorage */
81
+    private $keyStorage;
82
+
83
+    /** @var Update */
84
+    private $update;
85
+
86
+    /** @var Manager */
87
+    private $mountManager;
88
+
89
+    /** @var array remember for which path we execute the repair step to avoid recursions */
90
+    private $fixUnencryptedSizeOf = [];
91
+
92
+    /** @var  ArrayCache */
93
+    private $arrayCache;
94
+
95
+    /**
96
+     * @param array $parameters
97
+     * @param IManager $encryptionManager
98
+     * @param Util $util
99
+     * @param ILogger $logger
100
+     * @param IFile $fileHelper
101
+     * @param string $uid
102
+     * @param IStorage $keyStorage
103
+     * @param Update $update
104
+     * @param Manager $mountManager
105
+     * @param ArrayCache $arrayCache
106
+     */
107
+    public function __construct(
108
+        $parameters,
109
+        IManager $encryptionManager = null,
110
+        Util $util = null,
111
+        ILogger $logger = null,
112
+        IFile $fileHelper = null,
113
+        $uid = null,
114
+        IStorage $keyStorage = null,
115
+        Update $update = null,
116
+        Manager $mountManager = null,
117
+        ArrayCache $arrayCache = null
118
+    ) {
119
+        $this->mountPoint = $parameters['mountPoint'];
120
+        $this->mount = $parameters['mount'];
121
+        $this->encryptionManager = $encryptionManager;
122
+        $this->util = $util;
123
+        $this->logger = $logger;
124
+        $this->uid = $uid;
125
+        $this->fileHelper = $fileHelper;
126
+        $this->keyStorage = $keyStorage;
127
+        $this->unencryptedSize = [];
128
+        $this->update = $update;
129
+        $this->mountManager = $mountManager;
130
+        $this->arrayCache = $arrayCache;
131
+        parent::__construct($parameters);
132
+    }
133
+
134
+    /**
135
+     * see http://php.net/manual/en/function.filesize.php
136
+     * The result for filesize when called on a folder is required to be 0
137
+     *
138
+     * @param string $path
139
+     * @return int
140
+     */
141
+    public function filesize($path) {
142
+        $fullPath = $this->getFullPath($path);
143
+
144
+        /** @var CacheEntry $info */
145
+        $info = $this->getCache()->get($path);
146
+        if (isset($this->unencryptedSize[$fullPath])) {
147
+            $size = $this->unencryptedSize[$fullPath];
148
+            // update file cache
149
+            if ($info instanceof ICacheEntry) {
150
+                $info = $info->getData();
151
+                $info['encrypted'] = $info['encryptedVersion'];
152
+            } else {
153
+                if (!is_array($info)) {
154
+                    $info = [];
155
+                }
156
+                $info['encrypted'] = true;
157
+            }
158
+
159
+            $info['size'] = $size;
160
+            $this->getCache()->put($path, $info);
161
+
162
+            return $size;
163
+        }
164
+
165
+        if (isset($info['fileid']) && $info['encrypted']) {
166
+            return $this->verifyUnencryptedSize($path, $info['size']);
167
+        }
168
+
169
+        return $this->storage->filesize($path);
170
+    }
171
+
172
+    private function modifyMetaData(string $path, array $data): array {
173
+        $fullPath = $this->getFullPath($path);
174
+        $info = $this->getCache()->get($path);
175
+
176
+        if (isset($this->unencryptedSize[$fullPath])) {
177
+            $data['encrypted'] = true;
178
+            $data['size'] = $this->unencryptedSize[$fullPath];
179
+        } else {
180
+            if (isset($info['fileid']) && $info['encrypted']) {
181
+                $data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
182
+                $data['encrypted'] = true;
183
+            }
184
+        }
185
+
186
+        if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
187
+            $data['encryptedVersion'] = $info['encryptedVersion'];
188
+        }
189
+
190
+        return $data;
191
+    }
192
+
193
+    /**
194
+     * @param string $path
195
+     * @return array
196
+     */
197
+    public function getMetaData($path) {
198
+        $data = $this->storage->getMetaData($path);
199
+        if (is_null($data)) {
200
+            return null;
201
+        }
202
+        return $this->modifyMetaData($path, $data);
203
+    }
204
+
205
+    public function getDirectoryContent($directory): \Traversable {
206
+        $parent = rtrim($directory, '/');
207
+        foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
208
+            yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
209
+        }
210
+    }
211
+
212
+    /**
213
+     * see http://php.net/manual/en/function.file_get_contents.php
214
+     *
215
+     * @param string $path
216
+     * @return string
217
+     */
218
+    public function file_get_contents($path) {
219
+        $encryptionModule = $this->getEncryptionModule($path);
220
+
221
+        if ($encryptionModule) {
222
+            $handle = $this->fopen($path, "r");
223
+            if (!$handle) {
224
+                return false;
225
+            }
226
+            $data = stream_get_contents($handle);
227
+            fclose($handle);
228
+            return $data;
229
+        }
230
+        return $this->storage->file_get_contents($path);
231
+    }
232
+
233
+    /**
234
+     * see http://php.net/manual/en/function.file_put_contents.php
235
+     *
236
+     * @param string $path
237
+     * @param mixed $data
238
+     * @return int|false
239
+     */
240
+    public function file_put_contents($path, $data) {
241
+        // file put content will always be translated to a stream write
242
+        $handle = $this->fopen($path, 'w');
243
+        if (is_resource($handle)) {
244
+            $written = fwrite($handle, $data);
245
+            fclose($handle);
246
+            return $written;
247
+        }
248
+
249
+        return false;
250
+    }
251
+
252
+    /**
253
+     * see http://php.net/manual/en/function.unlink.php
254
+     *
255
+     * @param string $path
256
+     * @return bool
257
+     */
258
+    public function unlink($path) {
259
+        $fullPath = $this->getFullPath($path);
260
+        if ($this->util->isExcluded($fullPath)) {
261
+            return $this->storage->unlink($path);
262
+        }
263
+
264
+        $encryptionModule = $this->getEncryptionModule($path);
265
+        if ($encryptionModule) {
266
+            $this->keyStorage->deleteAllFileKeys($fullPath);
267
+        }
268
+
269
+        return $this->storage->unlink($path);
270
+    }
271
+
272
+    /**
273
+     * see http://php.net/manual/en/function.rename.php
274
+     *
275
+     * @param string $path1
276
+     * @param string $path2
277
+     * @return bool
278
+     */
279
+    public function rename($path1, $path2) {
280
+        $result = $this->storage->rename($path1, $path2);
281
+
282
+        if ($result &&
283
+            // versions always use the keys from the original file, so we can skip
284
+            // this step for versions
285
+            $this->isVersion($path2) === false &&
286
+            $this->encryptionManager->isEnabled()) {
287
+            $source = $this->getFullPath($path1);
288
+            if (!$this->util->isExcluded($source)) {
289
+                $target = $this->getFullPath($path2);
290
+                if (isset($this->unencryptedSize[$source])) {
291
+                    $this->unencryptedSize[$target] = $this->unencryptedSize[$source];
292
+                }
293
+                $this->keyStorage->renameKeys($source, $target);
294
+                $module = $this->getEncryptionModule($path2);
295
+                if ($module) {
296
+                    $module->update($target, $this->uid, []);
297
+                }
298
+            }
299
+        }
300
+
301
+        return $result;
302
+    }
303
+
304
+    /**
305
+     * see http://php.net/manual/en/function.rmdir.php
306
+     *
307
+     * @param string $path
308
+     * @return bool
309
+     */
310
+    public function rmdir($path) {
311
+        $result = $this->storage->rmdir($path);
312
+        $fullPath = $this->getFullPath($path);
313
+        if ($result &&
314
+            $this->util->isExcluded($fullPath) === false &&
315
+            $this->encryptionManager->isEnabled()
316
+        ) {
317
+            $this->keyStorage->deleteAllFileKeys($fullPath);
318
+        }
319
+
320
+        return $result;
321
+    }
322
+
323
+    /**
324
+     * check if a file can be read
325
+     *
326
+     * @param string $path
327
+     * @return bool
328
+     */
329
+    public function isReadable($path) {
330
+        $isReadable = true;
331
+
332
+        $metaData = $this->getMetaData($path);
333
+        if (
334
+            !$this->is_dir($path) &&
335
+            isset($metaData['encrypted']) &&
336
+            $metaData['encrypted'] === true
337
+        ) {
338
+            $fullPath = $this->getFullPath($path);
339
+            $module = $this->getEncryptionModule($path);
340
+            $isReadable = $module->isReadable($fullPath, $this->uid);
341
+        }
342
+
343
+        return $this->storage->isReadable($path) && $isReadable;
344
+    }
345
+
346
+    /**
347
+     * see http://php.net/manual/en/function.copy.php
348
+     *
349
+     * @param string $path1
350
+     * @param string $path2
351
+     * @return bool
352
+     */
353
+    public function copy($path1, $path2) {
354
+        $source = $this->getFullPath($path1);
355
+
356
+        if ($this->util->isExcluded($source)) {
357
+            return $this->storage->copy($path1, $path2);
358
+        }
359
+
360
+        // need to stream copy file by file in case we copy between a encrypted
361
+        // and a unencrypted storage
362
+        $this->unlink($path2);
363
+        return $this->copyFromStorage($this, $path1, $path2);
364
+    }
365
+
366
+    /**
367
+     * see http://php.net/manual/en/function.fopen.php
368
+     *
369
+     * @param string $path
370
+     * @param string $mode
371
+     * @return resource|bool
372
+     * @throws GenericEncryptionException
373
+     * @throws ModuleDoesNotExistsException
374
+     */
375
+    public function fopen($path, $mode) {
376
+
377
+        // check if the file is stored in the array cache, this means that we
378
+        // copy a file over to the versions folder, in this case we don't want to
379
+        // decrypt it
380
+        if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
381
+            $this->arrayCache->remove('encryption_copy_version_' . $path);
382
+            return $this->storage->fopen($path, $mode);
383
+        }
384
+
385
+        $encryptionEnabled = $this->encryptionManager->isEnabled();
386
+        $shouldEncrypt = false;
387
+        $encryptionModule = null;
388
+        $header = $this->getHeader($path);
389
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
390
+        $fullPath = $this->getFullPath($path);
391
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
392
+
393
+        if ($this->util->isExcluded($fullPath) === false) {
394
+            $size = $unencryptedSize = 0;
395
+            $realFile = $this->util->stripPartialFileExtension($path);
396
+            $targetExists = $this->file_exists($realFile) || $this->file_exists($path);
397
+            $targetIsEncrypted = false;
398
+            if ($targetExists) {
399
+                // in case the file exists we require the explicit module as
400
+                // specified in the file header - otherwise we need to fail hard to
401
+                // prevent data loss on client side
402
+                if (!empty($encryptionModuleId)) {
403
+                    $targetIsEncrypted = true;
404
+                    $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
405
+                }
406
+
407
+                if ($this->file_exists($path)) {
408
+                    $size = $this->storage->filesize($path);
409
+                    $unencryptedSize = $this->filesize($path);
410
+                } else {
411
+                    $size = $unencryptedSize = 0;
412
+                }
413
+            }
414
+
415
+            try {
416
+                if (
417
+                    $mode === 'w'
418
+                    || $mode === 'w+'
419
+                    || $mode === 'wb'
420
+                    || $mode === 'wb+'
421
+                ) {
422
+                    // if we update a encrypted file with a un-encrypted one we change the db flag
423
+                    if ($targetIsEncrypted && $encryptionEnabled === false) {
424
+                        $cache = $this->storage->getCache();
425
+                        if ($cache) {
426
+                            $entry = $cache->get($path);
427
+                            $cache->update($entry->getId(), ['encrypted' => 0]);
428
+                        }
429
+                    }
430
+                    if ($encryptionEnabled) {
431
+                        // if $encryptionModuleId is empty, the default module will be used
432
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
433
+                        $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
434
+                        $signed = true;
435
+                    }
436
+                } else {
437
+                    $info = $this->getCache()->get($path);
438
+                    // only get encryption module if we found one in the header
439
+                    // or if file should be encrypted according to the file cache
440
+                    if (!empty($encryptionModuleId)) {
441
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
442
+                        $shouldEncrypt = true;
443
+                    } elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
444
+                        // we come from a old installation. No header and/or no module defined
445
+                        // but the file is encrypted. In this case we need to use the
446
+                        // OC_DEFAULT_MODULE to read the file
447
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
448
+                        $shouldEncrypt = true;
449
+                        $targetIsEncrypted = true;
450
+                    }
451
+                }
452
+            } catch (ModuleDoesNotExistsException $e) {
453
+                $this->logger->logException($e, [
454
+                    'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
455
+                    'level' => ILogger::WARN,
456
+                    'app' => 'core',
457
+                ]);
458
+            }
459
+
460
+            // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
461
+            if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
462
+                if (!$targetExists || !$targetIsEncrypted) {
463
+                    $shouldEncrypt = false;
464
+                }
465
+            }
466
+
467
+            if ($shouldEncrypt === true && $encryptionModule !== null) {
468
+                $headerSize = $this->getHeaderSize($path);
469
+                $source = $this->storage->fopen($path, $mode);
470
+                if (!is_resource($source)) {
471
+                    return false;
472
+                }
473
+                $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
474
+                    $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
475
+                    $size, $unencryptedSize, $headerSize, $signed);
476
+                return $handle;
477
+            }
478
+        }
479
+
480
+        return $this->storage->fopen($path, $mode);
481
+    }
482
+
483
+
484
+    /**
485
+     * perform some plausibility checks if the the unencrypted size is correct.
486
+     * If not, we calculate the correct unencrypted size and return it
487
+     *
488
+     * @param string $path internal path relative to the storage root
489
+     * @param int $unencryptedSize size of the unencrypted file
490
+     *
491
+     * @return int unencrypted size
492
+     */
493
+    protected function verifyUnencryptedSize($path, $unencryptedSize) {
494
+        $size = $this->storage->filesize($path);
495
+        $result = $unencryptedSize;
496
+
497
+        if ($unencryptedSize < 0 ||
498
+            ($size > 0 && $unencryptedSize === $size)
499
+        ) {
500
+            // check if we already calculate the unencrypted size for the
501
+            // given path to avoid recursions
502
+            if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
503
+                $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
504
+                try {
505
+                    $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
506
+                } catch (\Exception $e) {
507
+                    $this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path);
508
+                    $this->logger->logException($e);
509
+                }
510
+                unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
511
+            }
512
+        }
513
+
514
+        return $result;
515
+    }
516
+
517
+    /**
518
+     * calculate the unencrypted size
519
+     *
520
+     * @param string $path internal path relative to the storage root
521
+     * @param int $size size of the physical file
522
+     * @param int $unencryptedSize size of the unencrypted file
523
+     *
524
+     * @return int calculated unencrypted size
525
+     */
526
+    protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
527
+        $headerSize = $this->getHeaderSize($path);
528
+        $header = $this->getHeader($path);
529
+        $encryptionModule = $this->getEncryptionModule($path);
530
+
531
+        $stream = $this->storage->fopen($path, 'r');
532
+
533
+        // if we couldn't open the file we return the old unencrypted size
534
+        if (!is_resource($stream)) {
535
+            $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
536
+            return $unencryptedSize;
537
+        }
538
+
539
+        $newUnencryptedSize = 0;
540
+        $size -= $headerSize;
541
+        $blockSize = $this->util->getBlockSize();
542
+
543
+        // if a header exists we skip it
544
+        if ($headerSize > 0) {
545
+            fread($stream, $headerSize);
546
+        }
547
+
548
+        // fast path, else the calculation for $lastChunkNr is bogus
549
+        if ($size === 0) {
550
+            return 0;
551
+        }
552
+
553
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
554
+        $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
555
+
556
+        // calculate last chunk nr
557
+        // next highest is end of chunks, one subtracted is last one
558
+        // we have to read the last chunk, we can't just calculate it (because of padding etc)
559
+
560
+        $lastChunkNr = ceil($size / $blockSize) - 1;
561
+        // calculate last chunk position
562
+        $lastChunkPos = ($lastChunkNr * $blockSize);
563
+        // try to fseek to the last chunk, if it fails we have to read the whole file
564
+        if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
565
+            $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
566
+        }
567
+
568
+        $lastChunkContentEncrypted = '';
569
+        $count = $blockSize;
570
+
571
+        while ($count > 0) {
572
+            $data = fread($stream, $blockSize);
573
+            $count = strlen($data);
574
+            $lastChunkContentEncrypted .= $data;
575
+            if (strlen($lastChunkContentEncrypted) > $blockSize) {
576
+                $newUnencryptedSize += $unencryptedBlockSize;
577
+                $lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize);
578
+            }
579
+        }
580
+
581
+        fclose($stream);
582
+
583
+        // we have to decrypt the last chunk to get it actual size
584
+        $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
585
+        $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
586
+        $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
587
+
588
+        // calc the real file size with the size of the last chunk
589
+        $newUnencryptedSize += strlen($decryptedLastChunk);
590
+
591
+        $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
592
+
593
+        // write to cache if applicable
594
+        $cache = $this->storage->getCache();
595
+        if ($cache) {
596
+            $entry = $cache->get($path);
597
+            $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
598
+        }
599
+
600
+        return $newUnencryptedSize;
601
+    }
602
+
603
+    /**
604
+     * @param Storage\IStorage $sourceStorage
605
+     * @param string $sourceInternalPath
606
+     * @param string $targetInternalPath
607
+     * @param bool $preserveMtime
608
+     * @return bool
609
+     */
610
+    public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
611
+        if ($sourceStorage === $this) {
612
+            return $this->rename($sourceInternalPath, $targetInternalPath);
613
+        }
614
+
615
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
616
+        // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
617
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
618
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
619
+        // - remove $this->copyBetweenStorage
620
+
621
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
622
+            return false;
623
+        }
624
+
625
+        $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
626
+        if ($result) {
627
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
628
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
629
+            } else {
630
+                $result &= $sourceStorage->unlink($sourceInternalPath);
631
+            }
632
+        }
633
+        return $result;
634
+    }
635
+
636
+
637
+    /**
638
+     * @param Storage\IStorage $sourceStorage
639
+     * @param string $sourceInternalPath
640
+     * @param string $targetInternalPath
641
+     * @param bool $preserveMtime
642
+     * @param bool $isRename
643
+     * @return bool
644
+     */
645
+    public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
646
+
647
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
648
+        // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
649
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
650
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
651
+        // - remove $this->copyBetweenStorage
652
+
653
+        return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
654
+    }
655
+
656
+    /**
657
+     * Update the encrypted cache version in the database
658
+     *
659
+     * @param Storage\IStorage $sourceStorage
660
+     * @param string $sourceInternalPath
661
+     * @param string $targetInternalPath
662
+     * @param bool $isRename
663
+     * @param bool $keepEncryptionVersion
664
+     */
665
+    private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
666
+        $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
667
+        $cacheInformation = [
668
+            'encrypted' => $isEncrypted,
669
+        ];
670
+        if ($isEncrypted) {
671
+            $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
672
+
673
+            // In case of a move operation from an unencrypted to an encrypted
674
+            // storage the old encrypted version would stay with "0" while the
675
+            // correct value would be "1". Thus we manually set the value to "1"
676
+            // for those cases.
677
+            // See also https://github.com/owncloud/core/issues/23078
678
+            if ($encryptedVersion === 0 || !$keepEncryptionVersion) {
679
+                $encryptedVersion = 1;
680
+            }
681
+
682
+            $cacheInformation['encryptedVersion'] = $encryptedVersion;
683
+        }
684
+
685
+        // in case of a rename we need to manipulate the source cache because
686
+        // this information will be kept for the new target
687
+        if ($isRename) {
688
+            $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
689
+        } else {
690
+            $this->getCache()->put($targetInternalPath, $cacheInformation);
691
+        }
692
+    }
693
+
694
+    /**
695
+     * copy file between two storages
696
+     *
697
+     * @param Storage\IStorage $sourceStorage
698
+     * @param string $sourceInternalPath
699
+     * @param string $targetInternalPath
700
+     * @param bool $preserveMtime
701
+     * @param bool $isRename
702
+     * @return bool
703
+     * @throws \Exception
704
+     */
705
+    private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
706
+
707
+        // for versions we have nothing to do, because versions should always use the
708
+        // key from the original file. Just create a 1:1 copy and done
709
+        if ($this->isVersion($targetInternalPath) ||
710
+            $this->isVersion($sourceInternalPath)) {
711
+            // remember that we try to create a version so that we can detect it during
712
+            // fopen($sourceInternalPath) and by-pass the encryption in order to
713
+            // create a 1:1 copy of the file
714
+            $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
715
+            $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
716
+            $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
717
+            if ($result) {
718
+                $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
719
+                // make sure that we update the unencrypted size for the version
720
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
721
+                    $this->updateUnencryptedSize(
722
+                        $this->getFullPath($targetInternalPath),
723
+                        $info['size']
724
+                    );
725
+                }
726
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
727
+            }
728
+            return $result;
729
+        }
730
+
731
+        // first copy the keys that we reuse the existing file key on the target location
732
+        // and don't create a new one which would break versions for example.
733
+        $mount = $this->mountManager->findByStorageId($sourceStorage->getId());
734
+        if (count($mount) === 1) {
735
+            $mountPoint = $mount[0]->getMountPoint();
736
+            $source = $mountPoint . '/' . $sourceInternalPath;
737
+            $target = $this->getFullPath($targetInternalPath);
738
+            $this->copyKeys($source, $target);
739
+        } else {
740
+            $this->logger->error('Could not find mount point, can\'t keep encryption keys');
741
+        }
742
+
743
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
744
+            $dh = $sourceStorage->opendir($sourceInternalPath);
745
+            $result = $this->mkdir($targetInternalPath);
746
+            if (is_resource($dh)) {
747
+                while ($result and ($file = readdir($dh)) !== false) {
748
+                    if (!Filesystem::isIgnoredDir($file)) {
749
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
750
+                    }
751
+                }
752
+            }
753
+        } else {
754
+            try {
755
+                $source = $sourceStorage->fopen($sourceInternalPath, 'r');
756
+                $target = $this->fopen($targetInternalPath, 'w');
757
+                [, $result] = \OC_Helper::streamCopy($source, $target);
758
+                fclose($source);
759
+                fclose($target);
760
+            } catch (\Exception $e) {
761
+                fclose($source);
762
+                fclose($target);
763
+                throw $e;
764
+            }
765
+            if ($result) {
766
+                if ($preserveMtime) {
767
+                    $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
768
+                }
769
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
770
+            } else {
771
+                // delete partially written target file
772
+                $this->unlink($targetInternalPath);
773
+                // delete cache entry that was created by fopen
774
+                $this->getCache()->remove($targetInternalPath);
775
+            }
776
+        }
777
+        return (bool)$result;
778
+    }
779
+
780
+    /**
781
+     * get the path to a local version of the file.
782
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
783
+     *
784
+     * @param string $path
785
+     * @return string
786
+     */
787
+    public function getLocalFile($path) {
788
+        if ($this->encryptionManager->isEnabled()) {
789
+            $cachedFile = $this->getCachedFile($path);
790
+            if (is_string($cachedFile)) {
791
+                return $cachedFile;
792
+            }
793
+        }
794
+        return $this->storage->getLocalFile($path);
795
+    }
796
+
797
+    /**
798
+     * Returns the wrapped storage's value for isLocal()
799
+     *
800
+     * @return bool wrapped storage's isLocal() value
801
+     */
802
+    public function isLocal() {
803
+        if ($this->encryptionManager->isEnabled()) {
804
+            return false;
805
+        }
806
+        return $this->storage->isLocal();
807
+    }
808
+
809
+    /**
810
+     * see http://php.net/manual/en/function.stat.php
811
+     * only the following keys are required in the result: size and mtime
812
+     *
813
+     * @param string $path
814
+     * @return array
815
+     */
816
+    public function stat($path) {
817
+        $stat = $this->storage->stat($path);
818
+        $fileSize = $this->filesize($path);
819
+        $stat['size'] = $fileSize;
820
+        $stat[7] = $fileSize;
821
+        $stat['hasHeader'] = $this->getHeaderSize($path) > 0;
822
+        return $stat;
823
+    }
824
+
825
+    /**
826
+     * see http://php.net/manual/en/function.hash.php
827
+     *
828
+     * @param string $type
829
+     * @param string $path
830
+     * @param bool $raw
831
+     * @return string
832
+     */
833
+    public function hash($type, $path, $raw = false) {
834
+        $fh = $this->fopen($path, 'rb');
835
+        $ctx = hash_init($type);
836
+        hash_update_stream($ctx, $fh);
837
+        fclose($fh);
838
+        return hash_final($ctx, $raw);
839
+    }
840
+
841
+    /**
842
+     * return full path, including mount point
843
+     *
844
+     * @param string $path relative to mount point
845
+     * @return string full path including mount point
846
+     */
847
+    protected function getFullPath($path) {
848
+        return Filesystem::normalizePath($this->mountPoint . '/' . $path);
849
+    }
850
+
851
+    /**
852
+     * read first block of encrypted file, typically this will contain the
853
+     * encryption header
854
+     *
855
+     * @param string $path
856
+     * @return string
857
+     */
858
+    protected function readFirstBlock($path) {
859
+        $firstBlock = '';
860
+        if ($this->storage->file_exists($path)) {
861
+            $handle = $this->storage->fopen($path, 'r');
862
+            $firstBlock = fread($handle, $this->util->getHeaderSize());
863
+            fclose($handle);
864
+        }
865
+        return $firstBlock;
866
+    }
867
+
868
+    /**
869
+     * return header size of given file
870
+     *
871
+     * @param string $path
872
+     * @return int
873
+     */
874
+    protected function getHeaderSize($path) {
875
+        $headerSize = 0;
876
+        $realFile = $this->util->stripPartialFileExtension($path);
877
+        if ($this->storage->file_exists($realFile)) {
878
+            $path = $realFile;
879
+        }
880
+        $firstBlock = $this->readFirstBlock($path);
881
+
882
+        if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
883
+            $headerSize = $this->util->getHeaderSize();
884
+        }
885
+
886
+        return $headerSize;
887
+    }
888
+
889
+    /**
890
+     * parse raw header to array
891
+     *
892
+     * @param string $rawHeader
893
+     * @return array
894
+     */
895
+    protected function parseRawHeader($rawHeader) {
896
+        $result = [];
897
+        if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
898
+            $header = $rawHeader;
899
+            $endAt = strpos($header, Util::HEADER_END);
900
+            if ($endAt !== false) {
901
+                $header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
902
+
903
+                // +1 to not start with an ':' which would result in empty element at the beginning
904
+                $exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
905
+
906
+                $element = array_shift($exploded);
907
+                while ($element !== Util::HEADER_END) {
908
+                    $result[$element] = array_shift($exploded);
909
+                    $element = array_shift($exploded);
910
+                }
911
+            }
912
+        }
913
+
914
+        return $result;
915
+    }
916
+
917
+    /**
918
+     * read header from file
919
+     *
920
+     * @param string $path
921
+     * @return array
922
+     */
923
+    protected function getHeader($path) {
924
+        $realFile = $this->util->stripPartialFileExtension($path);
925
+        $exists = $this->storage->file_exists($realFile);
926
+        if ($exists) {
927
+            $path = $realFile;
928
+        }
929
+
930
+        $firstBlock = $this->readFirstBlock($path);
931
+        $result = $this->parseRawHeader($firstBlock);
932
+
933
+        // if the header doesn't contain a encryption module we check if it is a
934
+        // legacy file. If true, we add the default encryption module
935
+        if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
936
+            if (!empty($result)) {
937
+                $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
938
+            } elseif ($exists) {
939
+                // if the header was empty we have to check first if it is a encrypted file at all
940
+                // We would do query to filecache only if we know that entry in filecache exists
941
+                $info = $this->getCache()->get($path);
942
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
943
+                    $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
944
+                }
945
+            }
946
+        }
947
+
948
+        return $result;
949
+    }
950
+
951
+    /**
952
+     * read encryption module needed to read/write the file located at $path
953
+     *
954
+     * @param string $path
955
+     * @return null|\OCP\Encryption\IEncryptionModule
956
+     * @throws ModuleDoesNotExistsException
957
+     * @throws \Exception
958
+     */
959
+    protected function getEncryptionModule($path) {
960
+        $encryptionModule = null;
961
+        $header = $this->getHeader($path);
962
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
963
+        if (!empty($encryptionModuleId)) {
964
+            try {
965
+                $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
966
+            } catch (ModuleDoesNotExistsException $e) {
967
+                $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
968
+                throw $e;
969
+            }
970
+        }
971
+
972
+        return $encryptionModule;
973
+    }
974
+
975
+    /**
976
+     * @param string $path
977
+     * @param int $unencryptedSize
978
+     */
979
+    public function updateUnencryptedSize($path, $unencryptedSize) {
980
+        $this->unencryptedSize[$path] = $unencryptedSize;
981
+    }
982
+
983
+    /**
984
+     * copy keys to new location
985
+     *
986
+     * @param string $source path relative to data/
987
+     * @param string $target path relative to data/
988
+     * @return bool
989
+     */
990
+    protected function copyKeys($source, $target) {
991
+        if (!$this->util->isExcluded($source)) {
992
+            return $this->keyStorage->copyKeys($source, $target);
993
+        }
994
+
995
+        return false;
996
+    }
997
+
998
+    /**
999
+     * check if path points to a files version
1000
+     *
1001
+     * @param $path
1002
+     * @return bool
1003
+     */
1004
+    protected function isVersion($path) {
1005
+        $normalized = Filesystem::normalizePath($path);
1006
+        return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1007
+    }
1008
+
1009
+    /**
1010
+     * check if the given storage should be encrypted or not
1011
+     *
1012
+     * @param $path
1013
+     * @return bool
1014
+     */
1015
+    protected function shouldEncrypt($path) {
1016
+        $fullPath = $this->getFullPath($path);
1017
+        $mountPointConfig = $this->mount->getOption('encrypt', true);
1018
+        if ($mountPointConfig === false) {
1019
+            return false;
1020
+        }
1021
+
1022
+        try {
1023
+            $encryptionModule = $this->getEncryptionModule($fullPath);
1024
+        } catch (ModuleDoesNotExistsException $e) {
1025
+            return false;
1026
+        }
1027
+
1028
+        if ($encryptionModule === null) {
1029
+            $encryptionModule = $this->encryptionManager->getEncryptionModule();
1030
+        }
1031
+
1032
+        return $encryptionModule->shouldEncrypt($fullPath);
1033
+    }
1034
+
1035
+    public function writeStream(string $path, $stream, int $size = null): int {
1036
+        // always fall back to fopen
1037
+        $target = $this->fopen($path, 'w');
1038
+        [$count, $result] = \OC_Helper::streamCopy($stream, $target);
1039
+        fclose($target);
1040
+        return $count;
1041
+    }
1042 1042
 }
Please login to merge, or discard this patch.