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