Completed
Pull Request — master (#9290)
by Robin
40:28 queued 11:52
created
lib/private/Files/Stream/Encryption.php 2 patches
Indentation   +468 added lines, -468 removed lines patch added patch discarded remove patch
@@ -33,473 +33,473 @@
 block discarded – undo
33 33
 
34 34
 class Encryption extends Wrapper {
35 35
 
36
-	/** @var \OC\Encryption\Util */
37
-	protected $util;
38
-
39
-	/** @var \OC\Encryption\File */
40
-	protected $file;
41
-
42
-	/** @var \OCP\Encryption\IEncryptionModule */
43
-	protected $encryptionModule;
44
-
45
-	/** @var \OC\Files\Storage\Storage */
46
-	protected $storage;
47
-
48
-	/** @var \OC\Files\Storage\Wrapper\Encryption */
49
-	protected $encryptionStorage;
50
-
51
-	/** @var string */
52
-	protected $internalPath;
53
-
54
-	/** @var string */
55
-	protected $cache;
56
-
57
-	/** @var integer */
58
-	protected $size;
59
-
60
-	/** @var integer */
61
-	protected $position;
62
-
63
-	/** @var integer */
64
-	protected $unencryptedSize;
65
-
66
-	/** @var integer */
67
-	protected $headerSize;
68
-
69
-	/** @var integer */
70
-	protected $unencryptedBlockSize;
71
-
72
-	/** @var array */
73
-	protected $header;
74
-
75
-	/** @var string */
76
-	protected $fullPath;
77
-
78
-	/** @var  bool */
79
-	protected $signed;
80
-
81
-	/**
82
-	 * header data returned by the encryption module, will be written to the file
83
-	 * in case of a write operation
84
-	 *
85
-	 * @var array
86
-	 */
87
-	protected $newHeader;
88
-
89
-	/**
90
-	 * user who perform the read/write operation null for public access
91
-	 *
92
-	 * @var string
93
-	 */
94
-	protected $uid;
95
-
96
-	/** @var bool */
97
-	protected $readOnly;
98
-
99
-	/** @var bool */
100
-	protected $writeFlag;
101
-
102
-	/** @var array */
103
-	protected $expectedContextProperties;
104
-
105
-	public function __construct() {
106
-		$this->expectedContextProperties = array(
107
-			'source',
108
-			'storage',
109
-			'internalPath',
110
-			'fullPath',
111
-			'encryptionModule',
112
-			'header',
113
-			'uid',
114
-			'file',
115
-			'util',
116
-			'size',
117
-			'unencryptedSize',
118
-			'encryptionStorage',
119
-			'headerSize',
120
-			'signed'
121
-		);
122
-	}
123
-
124
-
125
-	/**
126
-	 * Wraps a stream with the provided callbacks
127
-	 *
128
-	 * @param resource $source
129
-	 * @param string $internalPath relative to mount point
130
-	 * @param string $fullPath relative to data/
131
-	 * @param array $header
132
-	 * @param string $uid
133
-	 * @param \OCP\Encryption\IEncryptionModule $encryptionModule
134
-	 * @param \OC\Files\Storage\Storage $storage
135
-	 * @param \OC\Files\Storage\Wrapper\Encryption $encStorage
136
-	 * @param \OC\Encryption\Util $util
137
-	 * @param \OC\Encryption\File $file
138
-	 * @param string $mode
139
-	 * @param int $size
140
-	 * @param int $unencryptedSize
141
-	 * @param int $headerSize
142
-	 * @param bool $signed
143
-	 * @param string $wrapper stream wrapper class
144
-	 * @return resource
145
-	 *
146
-	 * @throws \BadMethodCallException
147
-	 */
148
-	public static function wrap($source, $internalPath, $fullPath, array $header,
149
-								$uid,
150
-								\OCP\Encryption\IEncryptionModule $encryptionModule,
151
-								\OC\Files\Storage\Storage $storage,
152
-								\OC\Files\Storage\Wrapper\Encryption $encStorage,
153
-								\OC\Encryption\Util $util,
154
-								 \OC\Encryption\File $file,
155
-								$mode,
156
-								$size,
157
-								$unencryptedSize,
158
-								$headerSize,
159
-								$signed,
160
-								$wrapper = Encryption::class) {
161
-
162
-		$context = stream_context_create(array(
163
-			'ocencryption' => array(
164
-				'source' => $source,
165
-				'storage' => $storage,
166
-				'internalPath' => $internalPath,
167
-				'fullPath' => $fullPath,
168
-				'encryptionModule' => $encryptionModule,
169
-				'header' => $header,
170
-				'uid' => $uid,
171
-				'util' => $util,
172
-				'file' => $file,
173
-				'size' => $size,
174
-				'unencryptedSize' => $unencryptedSize,
175
-				'encryptionStorage' => $encStorage,
176
-				'headerSize' => $headerSize,
177
-				'signed' => $signed
178
-			)
179
-		));
180
-
181
-		return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode);
182
-	}
183
-
184
-	/**
185
-	 * add stream wrapper
186
-	 *
187
-	 * @param resource $source
188
-	 * @param string $mode
189
-	 * @param resource $context
190
-	 * @param string $protocol
191
-	 * @param string $class
192
-	 * @return resource
193
-	 * @throws \BadMethodCallException
194
-	 */
195
-	protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') {
196
-		try {
197
-			stream_wrapper_register($protocol, $class);
198
-			if (self::isDirectoryHandle($source)) {
199
-				$wrapped = opendir($protocol . '://', $context);
200
-			} else {
201
-				$wrapped = fopen($protocol . '://', $mode, false, $context);
202
-			}
203
-		} catch (\BadMethodCallException $e) {
204
-			stream_wrapper_unregister($protocol);
205
-			throw $e;
206
-		}
207
-		stream_wrapper_unregister($protocol);
208
-		return $wrapped;
209
-	}
210
-
211
-	/**
212
-	 * Load the source from the stream context and return the context options
213
-	 *
214
-	 * @param string $name
215
-	 * @return array
216
-	 * @throws \BadMethodCallException
217
-	 */
218
-	protected function loadContext($name) {
219
-		$context = parent::loadContext($name);
220
-
221
-		foreach ($this->expectedContextProperties as $property) {
222
-			if (array_key_exists($property, $context)) {
223
-				$this->{$property} = $context[$property];
224
-			} else {
225
-				throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set');
226
-			}
227
-		}
228
-		return $context;
229
-
230
-	}
231
-
232
-	public function stream_open($path, $mode, $options, &$opened_path) {
233
-		$this->loadContext('ocencryption');
234
-
235
-		$this->position = 0;
236
-		$this->cache = '';
237
-		$this->writeFlag = false;
238
-		$this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed);
239
-
240
-		if (
241
-			$mode === 'w'
242
-			|| $mode === 'w+'
243
-			|| $mode === 'wb'
244
-			|| $mode === 'wb+'
245
-			|| $mode === 'r+'
246
-			|| $mode === 'rb+'
247
-		) {
248
-			$this->readOnly = false;
249
-		} else {
250
-			$this->readOnly = true;
251
-		}
252
-
253
-		$sharePath = $this->fullPath;
254
-		if (!$this->storage->file_exists($this->internalPath)) {
255
-			$sharePath = dirname($sharePath);
256
-		}
257
-
258
-		$accessList = [];
259
-		if ($this->encryptionModule->needDetailedAccessList()) {
260
-			$accessList = $this->file->getAccessList($sharePath);
261
-		}
262
-		$this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList);
263
-
264
-		if (
265
-			$mode === 'w'
266
-			|| $mode === 'w+'
267
-			|| $mode === 'wb'
268
-			|| $mode === 'wb+'
269
-		) {
270
-			// We're writing a new file so start write counter with 0 bytes
271
-			$this->unencryptedSize = 0;
272
-			$this->writeHeader();
273
-			$this->headerSize = $this->util->getHeaderSize();
274
-			$this->size = $this->headerSize;
275
-		} else {
276
-			$this->skipHeader();
277
-		}
278
-
279
-		return true;
280
-
281
-	}
282
-
283
-	public function stream_eof() {
284
-		return $this->position >= $this->unencryptedSize;
285
-	}
286
-
287
-	public function stream_read($count) {
288
-
289
-		$result = '';
290
-
291
-		$count = min($count, $this->unencryptedSize - $this->position);
292
-		while ($count > 0) {
293
-			$remainingLength = $count;
294
-			// update the cache of the current block
295
-			$this->readCache();
296
-			// determine the relative position in the current block
297
-			$blockPosition = ($this->position % $this->unencryptedBlockSize);
298
-			// if entire read inside current block then only position needs to be updated
299
-			if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
300
-				$result .= substr($this->cache, $blockPosition, $remainingLength);
301
-				$this->position += $remainingLength;
302
-				$count = 0;
303
-				// otherwise remainder of current block is fetched, the block is flushed and the position updated
304
-			} else {
305
-				$result .= substr($this->cache, $blockPosition);
306
-				$this->flush();
307
-				$this->position += ($this->unencryptedBlockSize - $blockPosition);
308
-				$count -= ($this->unencryptedBlockSize - $blockPosition);
309
-			}
310
-		}
311
-		return $result;
312
-
313
-	}
314
-
315
-	public function stream_write($data) {
316
-
317
-		$length = 0;
318
-		// loop over $data to fit it in 6126 sized unencrypted blocks
319
-		while (isset($data[0])) {
320
-			$remainingLength = strlen($data);
321
-
322
-			// set the cache to the current 6126 block
323
-			$this->readCache();
324
-
325
-			// for seekable streams the pointer is moved back to the beginning of the encrypted block
326
-			// flush will start writing there when the position moves to another block
327
-			$positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) *
328
-				$this->util->getBlockSize() + $this->headerSize;
329
-			$resultFseek = $this->parentStreamSeek($positionInFile);
330
-
331
-			// only allow writes on seekable streams, or at the end of the encrypted stream
332
-			if (!$this->readOnly && ($resultFseek || $positionInFile === $this->size)) {
333
-
334
-				// switch the writeFlag so flush() will write the block
335
-				$this->writeFlag = true;
336
-
337
-				// determine the relative position in the current block
338
-				$blockPosition = ($this->position % $this->unencryptedBlockSize);
339
-				// check if $data fits in current block
340
-				// if so, overwrite existing data (if any)
341
-				// update position and liberate $data
342
-				if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
343
-					$this->cache = substr($this->cache, 0, $blockPosition)
344
-						. $data . substr($this->cache, $blockPosition + $remainingLength);
345
-					$this->position += $remainingLength;
346
-					$length += $remainingLength;
347
-					$data = '';
348
-					// if $data doesn't fit the current block, the fill the current block and reiterate
349
-					// after the block is filled, it is flushed and $data is updatedxxx
350
-				} else {
351
-					$this->cache = substr($this->cache, 0, $blockPosition) .
352
-						substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
353
-					$this->flush();
354
-					$this->position += ($this->unencryptedBlockSize - $blockPosition);
355
-					$length += ($this->unencryptedBlockSize - $blockPosition);
356
-					$data = substr($data, $this->unencryptedBlockSize - $blockPosition);
357
-				}
358
-			} else {
359
-				$data = '';
360
-			}
361
-			$this->unencryptedSize = max($this->unencryptedSize, $this->position);
362
-		}
363
-		return $length;
364
-	}
365
-
366
-	public function stream_tell() {
367
-		return $this->position;
368
-	}
369
-
370
-	public function stream_seek($offset, $whence = SEEK_SET) {
371
-
372
-		$return = false;
373
-
374
-		switch ($whence) {
375
-			case SEEK_SET:
376
-				$newPosition = $offset;
377
-				break;
378
-			case SEEK_CUR:
379
-				$newPosition = $this->position + $offset;
380
-				break;
381
-			case SEEK_END:
382
-				$newPosition = $this->unencryptedSize + $offset;
383
-				break;
384
-			default:
385
-				return $return;
386
-		}
387
-
388
-		if ($newPosition > $this->unencryptedSize || $newPosition < 0) {
389
-			return $return;
390
-		}
391
-
392
-		$newFilePosition = floor($newPosition / $this->unencryptedBlockSize)
393
-			* $this->util->getBlockSize() + $this->headerSize;
394
-
395
-		$oldFilePosition = parent::stream_tell();
396
-		if ($this->parentStreamSeek($newFilePosition)) {
397
-			$this->parentStreamSeek($oldFilePosition);
398
-			$this->flush();
399
-			$this->parentStreamSeek($newFilePosition);
400
-			$this->position = $newPosition;
401
-			$return = true;
402
-		}
403
-		return $return;
404
-
405
-	}
406
-
407
-	public function stream_close() {
408
-		$this->flush('end');
409
-		$position = (int)floor($this->position/$this->unencryptedBlockSize);
410
-		$remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end');
411
-		if ($this->readOnly === false) {
412
-			if(!empty($remainingData)) {
413
-				parent::stream_write($remainingData);
414
-			}
415
-			$this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize);
416
-		}
417
-		return parent::stream_close();
418
-	}
419
-
420
-	/**
421
-	 * write block to file
422
-	 * @param string $positionPrefix
423
-	 */
424
-	protected function flush($positionPrefix = '') {
425
-		// write to disk only when writeFlag was set to 1
426
-		if ($this->writeFlag) {
427
-			// Disable the file proxies so that encryption is not
428
-			// automatically attempted when the file is written to disk -
429
-			// we are handling that separately here and we don't want to
430
-			// get into an infinite loop
431
-			$position = (int)floor($this->position/$this->unencryptedBlockSize);
432
-			$encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix);
433
-			$bytesWritten = parent::stream_write($encrypted);
434
-			$this->writeFlag = false;
435
-			// Check whether the write concerns the last block
436
-			// If so then update the encrypted filesize
437
-			// Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called
438
-			// We recalculate the encrypted filesize as we do not know the context of calling flush()
439
-			$completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize);
440
-			if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) {
441
-				$this->size = $this->util->getBlockSize() * $completeBlocksInFile;
442
-				$this->size += $bytesWritten;
443
-				$this->size += $this->headerSize;
444
-			}
445
-		}
446
-		// always empty the cache (otherwise readCache() will not fill it with the new block)
447
-		$this->cache = '';
448
-	}
449
-
450
-	/**
451
-	 * read block to file
452
-	 */
453
-	protected function readCache() {
454
-		// cache should always be empty string when this function is called
455
-		// don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block
456
-		if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) {
457
-			// Get the data from the file handle
458
-			$data = parent::stream_read($this->util->getBlockSize());
459
-			$position = (int)floor($this->position/$this->unencryptedBlockSize);
460
-			$numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize);
461
-			if($numberOfChunks === $position) {
462
-				$position .= 'end';
463
-			}
464
-			$this->cache = $this->encryptionModule->decrypt($data, $position);
465
-		}
466
-	}
467
-
468
-	/**
469
-	 * write header at beginning of encrypted file
470
-	 *
471
-	 * @return integer
472
-	 * @throws EncryptionHeaderKeyExistsException if header key is already in use
473
-	 */
474
-	protected function writeHeader() {
475
-		$header = $this->util->createHeader($this->newHeader, $this->encryptionModule);
476
-		return parent::stream_write($header);
477
-	}
478
-
479
-	/**
480
-	 * read first block to skip the header
481
-	 */
482
-	protected function skipHeader() {
483
-		parent::stream_read($this->headerSize);
484
-	}
485
-
486
-	/**
487
-	 * call stream_seek() from parent class
488
-	 *
489
-	 * @param integer $position
490
-	 * @return bool
491
-	 */
492
-	protected function parentStreamSeek($position) {
493
-		return parent::stream_seek($position);
494
-	}
495
-
496
-	/**
497
-	 * @param string $path
498
-	 * @param array $options
499
-	 * @return bool
500
-	 */
501
-	public function dir_opendir($path, $options) {
502
-		return false;
503
-	}
36
+    /** @var \OC\Encryption\Util */
37
+    protected $util;
38
+
39
+    /** @var \OC\Encryption\File */
40
+    protected $file;
41
+
42
+    /** @var \OCP\Encryption\IEncryptionModule */
43
+    protected $encryptionModule;
44
+
45
+    /** @var \OC\Files\Storage\Storage */
46
+    protected $storage;
47
+
48
+    /** @var \OC\Files\Storage\Wrapper\Encryption */
49
+    protected $encryptionStorage;
50
+
51
+    /** @var string */
52
+    protected $internalPath;
53
+
54
+    /** @var string */
55
+    protected $cache;
56
+
57
+    /** @var integer */
58
+    protected $size;
59
+
60
+    /** @var integer */
61
+    protected $position;
62
+
63
+    /** @var integer */
64
+    protected $unencryptedSize;
65
+
66
+    /** @var integer */
67
+    protected $headerSize;
68
+
69
+    /** @var integer */
70
+    protected $unencryptedBlockSize;
71
+
72
+    /** @var array */
73
+    protected $header;
74
+
75
+    /** @var string */
76
+    protected $fullPath;
77
+
78
+    /** @var  bool */
79
+    protected $signed;
80
+
81
+    /**
82
+     * header data returned by the encryption module, will be written to the file
83
+     * in case of a write operation
84
+     *
85
+     * @var array
86
+     */
87
+    protected $newHeader;
88
+
89
+    /**
90
+     * user who perform the read/write operation null for public access
91
+     *
92
+     * @var string
93
+     */
94
+    protected $uid;
95
+
96
+    /** @var bool */
97
+    protected $readOnly;
98
+
99
+    /** @var bool */
100
+    protected $writeFlag;
101
+
102
+    /** @var array */
103
+    protected $expectedContextProperties;
104
+
105
+    public function __construct() {
106
+        $this->expectedContextProperties = array(
107
+            'source',
108
+            'storage',
109
+            'internalPath',
110
+            'fullPath',
111
+            'encryptionModule',
112
+            'header',
113
+            'uid',
114
+            'file',
115
+            'util',
116
+            'size',
117
+            'unencryptedSize',
118
+            'encryptionStorage',
119
+            'headerSize',
120
+            'signed'
121
+        );
122
+    }
123
+
124
+
125
+    /**
126
+     * Wraps a stream with the provided callbacks
127
+     *
128
+     * @param resource $source
129
+     * @param string $internalPath relative to mount point
130
+     * @param string $fullPath relative to data/
131
+     * @param array $header
132
+     * @param string $uid
133
+     * @param \OCP\Encryption\IEncryptionModule $encryptionModule
134
+     * @param \OC\Files\Storage\Storage $storage
135
+     * @param \OC\Files\Storage\Wrapper\Encryption $encStorage
136
+     * @param \OC\Encryption\Util $util
137
+     * @param \OC\Encryption\File $file
138
+     * @param string $mode
139
+     * @param int $size
140
+     * @param int $unencryptedSize
141
+     * @param int $headerSize
142
+     * @param bool $signed
143
+     * @param string $wrapper stream wrapper class
144
+     * @return resource
145
+     *
146
+     * @throws \BadMethodCallException
147
+     */
148
+    public static function wrap($source, $internalPath, $fullPath, array $header,
149
+                                $uid,
150
+                                \OCP\Encryption\IEncryptionModule $encryptionModule,
151
+                                \OC\Files\Storage\Storage $storage,
152
+                                \OC\Files\Storage\Wrapper\Encryption $encStorage,
153
+                                \OC\Encryption\Util $util,
154
+                                    \OC\Encryption\File $file,
155
+                                $mode,
156
+                                $size,
157
+                                $unencryptedSize,
158
+                                $headerSize,
159
+                                $signed,
160
+                                $wrapper = Encryption::class) {
161
+
162
+        $context = stream_context_create(array(
163
+            'ocencryption' => array(
164
+                'source' => $source,
165
+                'storage' => $storage,
166
+                'internalPath' => $internalPath,
167
+                'fullPath' => $fullPath,
168
+                'encryptionModule' => $encryptionModule,
169
+                'header' => $header,
170
+                'uid' => $uid,
171
+                'util' => $util,
172
+                'file' => $file,
173
+                'size' => $size,
174
+                'unencryptedSize' => $unencryptedSize,
175
+                'encryptionStorage' => $encStorage,
176
+                'headerSize' => $headerSize,
177
+                'signed' => $signed
178
+            )
179
+        ));
180
+
181
+        return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode);
182
+    }
183
+
184
+    /**
185
+     * add stream wrapper
186
+     *
187
+     * @param resource $source
188
+     * @param string $mode
189
+     * @param resource $context
190
+     * @param string $protocol
191
+     * @param string $class
192
+     * @return resource
193
+     * @throws \BadMethodCallException
194
+     */
195
+    protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') {
196
+        try {
197
+            stream_wrapper_register($protocol, $class);
198
+            if (self::isDirectoryHandle($source)) {
199
+                $wrapped = opendir($protocol . '://', $context);
200
+            } else {
201
+                $wrapped = fopen($protocol . '://', $mode, false, $context);
202
+            }
203
+        } catch (\BadMethodCallException $e) {
204
+            stream_wrapper_unregister($protocol);
205
+            throw $e;
206
+        }
207
+        stream_wrapper_unregister($protocol);
208
+        return $wrapped;
209
+    }
210
+
211
+    /**
212
+     * Load the source from the stream context and return the context options
213
+     *
214
+     * @param string $name
215
+     * @return array
216
+     * @throws \BadMethodCallException
217
+     */
218
+    protected function loadContext($name) {
219
+        $context = parent::loadContext($name);
220
+
221
+        foreach ($this->expectedContextProperties as $property) {
222
+            if (array_key_exists($property, $context)) {
223
+                $this->{$property} = $context[$property];
224
+            } else {
225
+                throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set');
226
+            }
227
+        }
228
+        return $context;
229
+
230
+    }
231
+
232
+    public function stream_open($path, $mode, $options, &$opened_path) {
233
+        $this->loadContext('ocencryption');
234
+
235
+        $this->position = 0;
236
+        $this->cache = '';
237
+        $this->writeFlag = false;
238
+        $this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed);
239
+
240
+        if (
241
+            $mode === 'w'
242
+            || $mode === 'w+'
243
+            || $mode === 'wb'
244
+            || $mode === 'wb+'
245
+            || $mode === 'r+'
246
+            || $mode === 'rb+'
247
+        ) {
248
+            $this->readOnly = false;
249
+        } else {
250
+            $this->readOnly = true;
251
+        }
252
+
253
+        $sharePath = $this->fullPath;
254
+        if (!$this->storage->file_exists($this->internalPath)) {
255
+            $sharePath = dirname($sharePath);
256
+        }
257
+
258
+        $accessList = [];
259
+        if ($this->encryptionModule->needDetailedAccessList()) {
260
+            $accessList = $this->file->getAccessList($sharePath);
261
+        }
262
+        $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList);
263
+
264
+        if (
265
+            $mode === 'w'
266
+            || $mode === 'w+'
267
+            || $mode === 'wb'
268
+            || $mode === 'wb+'
269
+        ) {
270
+            // We're writing a new file so start write counter with 0 bytes
271
+            $this->unencryptedSize = 0;
272
+            $this->writeHeader();
273
+            $this->headerSize = $this->util->getHeaderSize();
274
+            $this->size = $this->headerSize;
275
+        } else {
276
+            $this->skipHeader();
277
+        }
278
+
279
+        return true;
280
+
281
+    }
282
+
283
+    public function stream_eof() {
284
+        return $this->position >= $this->unencryptedSize;
285
+    }
286
+
287
+    public function stream_read($count) {
288
+
289
+        $result = '';
290
+
291
+        $count = min($count, $this->unencryptedSize - $this->position);
292
+        while ($count > 0) {
293
+            $remainingLength = $count;
294
+            // update the cache of the current block
295
+            $this->readCache();
296
+            // determine the relative position in the current block
297
+            $blockPosition = ($this->position % $this->unencryptedBlockSize);
298
+            // if entire read inside current block then only position needs to be updated
299
+            if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
300
+                $result .= substr($this->cache, $blockPosition, $remainingLength);
301
+                $this->position += $remainingLength;
302
+                $count = 0;
303
+                // otherwise remainder of current block is fetched, the block is flushed and the position updated
304
+            } else {
305
+                $result .= substr($this->cache, $blockPosition);
306
+                $this->flush();
307
+                $this->position += ($this->unencryptedBlockSize - $blockPosition);
308
+                $count -= ($this->unencryptedBlockSize - $blockPosition);
309
+            }
310
+        }
311
+        return $result;
312
+
313
+    }
314
+
315
+    public function stream_write($data) {
316
+
317
+        $length = 0;
318
+        // loop over $data to fit it in 6126 sized unencrypted blocks
319
+        while (isset($data[0])) {
320
+            $remainingLength = strlen($data);
321
+
322
+            // set the cache to the current 6126 block
323
+            $this->readCache();
324
+
325
+            // for seekable streams the pointer is moved back to the beginning of the encrypted block
326
+            // flush will start writing there when the position moves to another block
327
+            $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) *
328
+                $this->util->getBlockSize() + $this->headerSize;
329
+            $resultFseek = $this->parentStreamSeek($positionInFile);
330
+
331
+            // only allow writes on seekable streams, or at the end of the encrypted stream
332
+            if (!$this->readOnly && ($resultFseek || $positionInFile === $this->size)) {
333
+
334
+                // switch the writeFlag so flush() will write the block
335
+                $this->writeFlag = true;
336
+
337
+                // determine the relative position in the current block
338
+                $blockPosition = ($this->position % $this->unencryptedBlockSize);
339
+                // check if $data fits in current block
340
+                // if so, overwrite existing data (if any)
341
+                // update position and liberate $data
342
+                if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
343
+                    $this->cache = substr($this->cache, 0, $blockPosition)
344
+                        . $data . substr($this->cache, $blockPosition + $remainingLength);
345
+                    $this->position += $remainingLength;
346
+                    $length += $remainingLength;
347
+                    $data = '';
348
+                    // if $data doesn't fit the current block, the fill the current block and reiterate
349
+                    // after the block is filled, it is flushed and $data is updatedxxx
350
+                } else {
351
+                    $this->cache = substr($this->cache, 0, $blockPosition) .
352
+                        substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
353
+                    $this->flush();
354
+                    $this->position += ($this->unencryptedBlockSize - $blockPosition);
355
+                    $length += ($this->unencryptedBlockSize - $blockPosition);
356
+                    $data = substr($data, $this->unencryptedBlockSize - $blockPosition);
357
+                }
358
+            } else {
359
+                $data = '';
360
+            }
361
+            $this->unencryptedSize = max($this->unencryptedSize, $this->position);
362
+        }
363
+        return $length;
364
+    }
365
+
366
+    public function stream_tell() {
367
+        return $this->position;
368
+    }
369
+
370
+    public function stream_seek($offset, $whence = SEEK_SET) {
371
+
372
+        $return = false;
373
+
374
+        switch ($whence) {
375
+            case SEEK_SET:
376
+                $newPosition = $offset;
377
+                break;
378
+            case SEEK_CUR:
379
+                $newPosition = $this->position + $offset;
380
+                break;
381
+            case SEEK_END:
382
+                $newPosition = $this->unencryptedSize + $offset;
383
+                break;
384
+            default:
385
+                return $return;
386
+        }
387
+
388
+        if ($newPosition > $this->unencryptedSize || $newPosition < 0) {
389
+            return $return;
390
+        }
391
+
392
+        $newFilePosition = floor($newPosition / $this->unencryptedBlockSize)
393
+            * $this->util->getBlockSize() + $this->headerSize;
394
+
395
+        $oldFilePosition = parent::stream_tell();
396
+        if ($this->parentStreamSeek($newFilePosition)) {
397
+            $this->parentStreamSeek($oldFilePosition);
398
+            $this->flush();
399
+            $this->parentStreamSeek($newFilePosition);
400
+            $this->position = $newPosition;
401
+            $return = true;
402
+        }
403
+        return $return;
404
+
405
+    }
406
+
407
+    public function stream_close() {
408
+        $this->flush('end');
409
+        $position = (int)floor($this->position/$this->unencryptedBlockSize);
410
+        $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end');
411
+        if ($this->readOnly === false) {
412
+            if(!empty($remainingData)) {
413
+                parent::stream_write($remainingData);
414
+            }
415
+            $this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize);
416
+        }
417
+        return parent::stream_close();
418
+    }
419
+
420
+    /**
421
+     * write block to file
422
+     * @param string $positionPrefix
423
+     */
424
+    protected function flush($positionPrefix = '') {
425
+        // write to disk only when writeFlag was set to 1
426
+        if ($this->writeFlag) {
427
+            // Disable the file proxies so that encryption is not
428
+            // automatically attempted when the file is written to disk -
429
+            // we are handling that separately here and we don't want to
430
+            // get into an infinite loop
431
+            $position = (int)floor($this->position/$this->unencryptedBlockSize);
432
+            $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix);
433
+            $bytesWritten = parent::stream_write($encrypted);
434
+            $this->writeFlag = false;
435
+            // Check whether the write concerns the last block
436
+            // If so then update the encrypted filesize
437
+            // Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called
438
+            // We recalculate the encrypted filesize as we do not know the context of calling flush()
439
+            $completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize);
440
+            if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) {
441
+                $this->size = $this->util->getBlockSize() * $completeBlocksInFile;
442
+                $this->size += $bytesWritten;
443
+                $this->size += $this->headerSize;
444
+            }
445
+        }
446
+        // always empty the cache (otherwise readCache() will not fill it with the new block)
447
+        $this->cache = '';
448
+    }
449
+
450
+    /**
451
+     * read block to file
452
+     */
453
+    protected function readCache() {
454
+        // cache should always be empty string when this function is called
455
+        // don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block
456
+        if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) {
457
+            // Get the data from the file handle
458
+            $data = parent::stream_read($this->util->getBlockSize());
459
+            $position = (int)floor($this->position/$this->unencryptedBlockSize);
460
+            $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize);
461
+            if($numberOfChunks === $position) {
462
+                $position .= 'end';
463
+            }
464
+            $this->cache = $this->encryptionModule->decrypt($data, $position);
465
+        }
466
+    }
467
+
468
+    /**
469
+     * write header at beginning of encrypted file
470
+     *
471
+     * @return integer
472
+     * @throws EncryptionHeaderKeyExistsException if header key is already in use
473
+     */
474
+    protected function writeHeader() {
475
+        $header = $this->util->createHeader($this->newHeader, $this->encryptionModule);
476
+        return parent::stream_write($header);
477
+    }
478
+
479
+    /**
480
+     * read first block to skip the header
481
+     */
482
+    protected function skipHeader() {
483
+        parent::stream_read($this->headerSize);
484
+    }
485
+
486
+    /**
487
+     * call stream_seek() from parent class
488
+     *
489
+     * @param integer $position
490
+     * @return bool
491
+     */
492
+    protected function parentStreamSeek($position) {
493
+        return parent::stream_seek($position);
494
+    }
495
+
496
+    /**
497
+     * @param string $path
498
+     * @param array $options
499
+     * @return bool
500
+     */
501
+    public function dir_opendir($path, $options) {
502
+        return false;
503
+    }
504 504
 
505 505
 }
Please login to merge, or discard this patch.
Spacing   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -196,9 +196,9 @@  discard block
 block discarded – undo
196 196
 		try {
197 197
 			stream_wrapper_register($protocol, $class);
198 198
 			if (self::isDirectoryHandle($source)) {
199
-				$wrapped = opendir($protocol . '://', $context);
199
+				$wrapped = opendir($protocol.'://', $context);
200 200
 			} else {
201
-				$wrapped = fopen($protocol . '://', $mode, false, $context);
201
+				$wrapped = fopen($protocol.'://', $mode, false, $context);
202 202
 			}
203 203
 		} catch (\BadMethodCallException $e) {
204 204
 			stream_wrapper_unregister($protocol);
@@ -222,7 +222,7 @@  discard block
 block discarded – undo
222 222
 			if (array_key_exists($property, $context)) {
223 223
 				$this->{$property} = $context[$property];
224 224
 			} else {
225
-				throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set');
225
+				throw new \BadMethodCallException('Invalid context, "'.$property.'" options not set');
226 226
 			}
227 227
 		}
228 228
 		return $context;
@@ -324,7 +324,7 @@  discard block
 block discarded – undo
324 324
 
325 325
 			// for seekable streams the pointer is moved back to the beginning of the encrypted block
326 326
 			// flush will start writing there when the position moves to another block
327
-			$positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) *
327
+			$positionInFile = (int) floor($this->position / $this->unencryptedBlockSize) *
328 328
 				$this->util->getBlockSize() + $this->headerSize;
329 329
 			$resultFseek = $this->parentStreamSeek($positionInFile);
330 330
 
@@ -341,14 +341,14 @@  discard block
 block discarded – undo
341 341
 				// update position and liberate $data
342 342
 				if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
343 343
 					$this->cache = substr($this->cache, 0, $blockPosition)
344
-						. $data . substr($this->cache, $blockPosition + $remainingLength);
344
+						. $data.substr($this->cache, $blockPosition + $remainingLength);
345 345
 					$this->position += $remainingLength;
346 346
 					$length += $remainingLength;
347 347
 					$data = '';
348 348
 					// if $data doesn't fit the current block, the fill the current block and reiterate
349 349
 					// after the block is filled, it is flushed and $data is updatedxxx
350 350
 				} else {
351
-					$this->cache = substr($this->cache, 0, $blockPosition) .
351
+					$this->cache = substr($this->cache, 0, $blockPosition).
352 352
 						substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
353 353
 					$this->flush();
354 354
 					$this->position += ($this->unencryptedBlockSize - $blockPosition);
@@ -406,10 +406,10 @@  discard block
 block discarded – undo
406 406
 
407 407
 	public function stream_close() {
408 408
 		$this->flush('end');
409
-		$position = (int)floor($this->position/$this->unencryptedBlockSize);
410
-		$remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end');
409
+		$position = (int) floor($this->position / $this->unencryptedBlockSize);
410
+		$remainingData = $this->encryptionModule->end($this->fullPath, $position.'end');
411 411
 		if ($this->readOnly === false) {
412
-			if(!empty($remainingData)) {
412
+			if (!empty($remainingData)) {
413 413
 				parent::stream_write($remainingData);
414 414
 			}
415 415
 			$this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize);
@@ -428,16 +428,16 @@  discard block
 block discarded – undo
428 428
 			// automatically attempted when the file is written to disk -
429 429
 			// we are handling that separately here and we don't want to
430 430
 			// get into an infinite loop
431
-			$position = (int)floor($this->position/$this->unencryptedBlockSize);
432
-			$encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix);
431
+			$position = (int) floor($this->position / $this->unencryptedBlockSize);
432
+			$encrypted = $this->encryptionModule->encrypt($this->cache, $position.$positionPrefix);
433 433
 			$bytesWritten = parent::stream_write($encrypted);
434 434
 			$this->writeFlag = false;
435 435
 			// Check whether the write concerns the last block
436 436
 			// If so then update the encrypted filesize
437 437
 			// Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called
438 438
 			// We recalculate the encrypted filesize as we do not know the context of calling flush()
439
-			$completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize);
440
-			if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) {
439
+			$completeBlocksInFile = (int) floor($this->unencryptedSize / $this->unencryptedBlockSize);
440
+			if ($completeBlocksInFile === (int) floor($this->position / $this->unencryptedBlockSize)) {
441 441
 				$this->size = $this->util->getBlockSize() * $completeBlocksInFile;
442 442
 				$this->size += $bytesWritten;
443 443
 				$this->size += $this->headerSize;
@@ -456,9 +456,9 @@  discard block
 block discarded – undo
456 456
 		if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) {
457 457
 			// Get the data from the file handle
458 458
 			$data = parent::stream_read($this->util->getBlockSize());
459
-			$position = (int)floor($this->position/$this->unencryptedBlockSize);
460
-			$numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize);
461
-			if($numberOfChunks === $position) {
459
+			$position = (int) floor($this->position / $this->unencryptedBlockSize);
460
+			$numberOfChunks = (int) ($this->unencryptedSize / $this->unencryptedBlockSize);
461
+			if ($numberOfChunks === $position) {
462 462
 				$position .= 'end';
463 463
 			}
464 464
 			$this->cache = $this->encryptionModule->decrypt($data, $position);
Please login to merge, or discard this patch.