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