Passed
Push — master ( 72fda1...adc4f1 )
by Roeland
18:06 queued 05:03
created
lib/private/AppFramework/Http/Request.php 1 patch
Indentation   +871 added lines, -871 removed lines patch added patch discarded remove patch
@@ -63,875 +63,875 @@
 block discarded – undo
63 63
  * @property mixed[] server
64 64
  */
65 65
 class Request implements \ArrayAccess, \Countable, IRequest {
66
-	public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
67
-	// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
68
-	public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/';
69
-	// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
70
-	public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
71
-	// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
72
-	public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/';
73
-	// Safari User Agent from http://www.useragentstring.com/pages/Safari/
74
-	public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
75
-	// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
76
-	public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
77
-	public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
78
-	public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
79
-
80
-	/**
81
-	 * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead
82
-	 */
83
-	public const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/';
84
-	/**
85
-	 * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_ANDROID instead
86
-	 */
87
-	public const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/';
88
-	/**
89
-	 * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_DESKTOP instead
90
-	 */
91
-	public const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/';
92
-
93
-	protected $inputStream;
94
-	protected $content;
95
-	protected $items = [];
96
-	protected $allowedKeys = [
97
-		'get',
98
-		'post',
99
-		'files',
100
-		'server',
101
-		'env',
102
-		'cookies',
103
-		'urlParams',
104
-		'parameters',
105
-		'method',
106
-		'requesttoken',
107
-	];
108
-	/** @var ISecureRandom */
109
-	protected $secureRandom;
110
-	/** @var IConfig */
111
-	protected $config;
112
-	/** @var string */
113
-	protected $requestId = '';
114
-	/** @var ICrypto */
115
-	protected $crypto;
116
-	/** @var CsrfTokenManager|null */
117
-	protected $csrfTokenManager;
118
-
119
-	/** @var bool */
120
-	protected $contentDecoded = false;
121
-
122
-	/**
123
-	 * @param array $vars An associative array with the following optional values:
124
-	 *        - array 'urlParams' the parameters which were matched from the URL
125
-	 *        - array 'get' the $_GET array
126
-	 *        - array|string 'post' the $_POST array or JSON string
127
-	 *        - array 'files' the $_FILES array
128
-	 *        - array 'server' the $_SERVER array
129
-	 *        - array 'env' the $_ENV array
130
-	 *        - array 'cookies' the $_COOKIE array
131
-	 *        - string 'method' the request method (GET, POST etc)
132
-	 *        - string|false 'requesttoken' the requesttoken or false when not available
133
-	 * @param ISecureRandom $secureRandom
134
-	 * @param IConfig $config
135
-	 * @param CsrfTokenManager|null $csrfTokenManager
136
-	 * @param string $stream
137
-	 * @see https://www.php.net/manual/en/reserved.variables.php
138
-	 */
139
-	public function __construct(array $vars,
140
-								ISecureRandom $secureRandom,
141
-								IConfig $config,
142
-								CsrfTokenManager $csrfTokenManager = null,
143
-								string $stream = 'php://input') {
144
-		$this->inputStream = $stream;
145
-		$this->items['params'] = [];
146
-		$this->secureRandom = $secureRandom;
147
-		$this->config = $config;
148
-		$this->csrfTokenManager = $csrfTokenManager;
149
-
150
-		if (!array_key_exists('method', $vars)) {
151
-			$vars['method'] = 'GET';
152
-		}
153
-
154
-		foreach ($this->allowedKeys as $name) {
155
-			$this->items[$name] = isset($vars[$name])
156
-				? $vars[$name]
157
-				: [];
158
-		}
159
-
160
-		$this->items['parameters'] = array_merge(
161
-			$this->items['get'],
162
-			$this->items['post'],
163
-			$this->items['urlParams'],
164
-			$this->items['params']
165
-		);
166
-	}
167
-	/**
168
-	 * @param array $parameters
169
-	 */
170
-	public function setUrlParameters(array $parameters) {
171
-		$this->items['urlParams'] = $parameters;
172
-		$this->items['parameters'] = array_merge(
173
-			$this->items['parameters'],
174
-			$this->items['urlParams']
175
-		);
176
-	}
177
-
178
-	/**
179
-	 * Countable method
180
-	 * @return int
181
-	 */
182
-	public function count(): int {
183
-		return \count($this->items['parameters']);
184
-	}
185
-
186
-	/**
187
-	 * ArrayAccess methods
188
-	 *
189
-	 * Gives access to the combined GET, POST and urlParams arrays
190
-	 *
191
-	 * Examples:
192
-	 *
193
-	 * $var = $request['myvar'];
194
-	 *
195
-	 * or
196
-	 *
197
-	 * if(!isset($request['myvar']) {
198
-	 * 	// Do something
199
-	 * }
200
-	 *
201
-	 * $request['myvar'] = 'something'; // This throws an exception.
202
-	 *
203
-	 * @param string $offset The key to lookup
204
-	 * @return boolean
205
-	 */
206
-	public function offsetExists($offset): bool {
207
-		return isset($this->items['parameters'][$offset]);
208
-	}
209
-
210
-	/**
211
-	 * @see offsetExists
212
-	 * @param string $offset
213
-	 * @return mixed
214
-	 */
215
-	public function offsetGet($offset) {
216
-		return isset($this->items['parameters'][$offset])
217
-			? $this->items['parameters'][$offset]
218
-			: null;
219
-	}
220
-
221
-	/**
222
-	 * @see offsetExists
223
-	 * @param string $offset
224
-	 * @param mixed $value
225
-	 */
226
-	public function offsetSet($offset, $value) {
227
-		throw new \RuntimeException('You cannot change the contents of the request object');
228
-	}
229
-
230
-	/**
231
-	 * @see offsetExists
232
-	 * @param string $offset
233
-	 */
234
-	public function offsetUnset($offset) {
235
-		throw new \RuntimeException('You cannot change the contents of the request object');
236
-	}
237
-
238
-	/**
239
-	 * Magic property accessors
240
-	 * @param string $name
241
-	 * @param mixed $value
242
-	 */
243
-	public function __set($name, $value) {
244
-		throw new \RuntimeException('You cannot change the contents of the request object');
245
-	}
246
-
247
-	/**
248
-	 * Access request variables by method and name.
249
-	 * Examples:
250
-	 *
251
-	 * $request->post['myvar']; // Only look for POST variables
252
-	 * $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
253
-	 * Looks in the combined GET, POST and urlParams array.
254
-	 *
255
-	 * If you access e.g. ->post but the current HTTP request method
256
-	 * is GET a \LogicException will be thrown.
257
-	 *
258
-	 * @param string $name The key to look for.
259
-	 * @throws \LogicException
260
-	 * @return mixed|null
261
-	 */
262
-	public function __get($name) {
263
-		switch ($name) {
264
-			case 'put':
265
-			case 'patch':
266
-			case 'get':
267
-			case 'post':
268
-				if ($this->method !== strtoupper($name)) {
269
-					throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
270
-				}
271
-				return $this->getContent();
272
-			case 'files':
273
-			case 'server':
274
-			case 'env':
275
-			case 'cookies':
276
-			case 'urlParams':
277
-			case 'method':
278
-				return isset($this->items[$name])
279
-					? $this->items[$name]
280
-					: null;
281
-			case 'parameters':
282
-			case 'params':
283
-				return $this->getContent();
284
-			default:
285
-				return isset($this[$name])
286
-					? $this[$name]
287
-					: null;
288
-		}
289
-	}
290
-
291
-	/**
292
-	 * @param string $name
293
-	 * @return bool
294
-	 */
295
-	public function __isset($name) {
296
-		if (\in_array($name, $this->allowedKeys, true)) {
297
-			return true;
298
-		}
299
-		return isset($this->items['parameters'][$name]);
300
-	}
301
-
302
-	/**
303
-	 * @param string $id
304
-	 */
305
-	public function __unset($id) {
306
-		throw new \RuntimeException('You cannot change the contents of the request object');
307
-	}
308
-
309
-	/**
310
-	 * Returns the value for a specific http header.
311
-	 *
312
-	 * This method returns an empty string if the header did not exist.
313
-	 *
314
-	 * @param string $name
315
-	 * @return string
316
-	 */
317
-	public function getHeader(string $name): string {
318
-		$name = strtoupper(str_replace('-', '_',$name));
319
-		if (isset($this->server['HTTP_' . $name])) {
320
-			return $this->server['HTTP_' . $name];
321
-		}
322
-
323
-		// There's a few headers that seem to end up in the top-level
324
-		// server array.
325
-		switch ($name) {
326
-			case 'CONTENT_TYPE':
327
-			case 'CONTENT_LENGTH':
328
-			case 'REMOTE_ADDR':
329
-				if (isset($this->server[$name])) {
330
-					return $this->server[$name];
331
-				}
332
-				break;
333
-		}
334
-
335
-		return '';
336
-	}
337
-
338
-	/**
339
-	 * Lets you access post and get parameters by the index
340
-	 * In case of json requests the encoded json body is accessed
341
-	 *
342
-	 * @param string $key the key which you want to access in the URL Parameter
343
-	 *                     placeholder, $_POST or $_GET array.
344
-	 *                     The priority how they're returned is the following:
345
-	 *                     1. URL parameters
346
-	 *                     2. POST parameters
347
-	 *                     3. GET parameters
348
-	 * @param mixed $default If the key is not found, this value will be returned
349
-	 * @return mixed the content of the array
350
-	 */
351
-	public function getParam(string $key, $default = null) {
352
-		return isset($this->parameters[$key])
353
-			? $this->parameters[$key]
354
-			: $default;
355
-	}
356
-
357
-	/**
358
-	 * Returns all params that were received, be it from the request
359
-	 * (as GET or POST) or throuh the URL by the route
360
-	 * @return array the array with all parameters
361
-	 */
362
-	public function getParams(): array {
363
-		return is_array($this->parameters) ? $this->parameters : [];
364
-	}
365
-
366
-	/**
367
-	 * Returns the method of the request
368
-	 * @return string the method of the request (POST, GET, etc)
369
-	 */
370
-	public function getMethod(): string {
371
-		return $this->method;
372
-	}
373
-
374
-	/**
375
-	 * Shortcut for accessing an uploaded file through the $_FILES array
376
-	 * @param string $key the key that will be taken from the $_FILES array
377
-	 * @return array the file in the $_FILES element
378
-	 */
379
-	public function getUploadedFile(string $key) {
380
-		return isset($this->files[$key]) ? $this->files[$key] : null;
381
-	}
382
-
383
-	/**
384
-	 * Shortcut for getting env variables
385
-	 * @param string $key the key that will be taken from the $_ENV array
386
-	 * @return array the value in the $_ENV element
387
-	 */
388
-	public function getEnv(string $key) {
389
-		return isset($this->env[$key]) ? $this->env[$key] : null;
390
-	}
391
-
392
-	/**
393
-	 * Shortcut for getting cookie variables
394
-	 * @param string $key the key that will be taken from the $_COOKIE array
395
-	 * @return string the value in the $_COOKIE element
396
-	 */
397
-	public function getCookie(string $key) {
398
-		return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
399
-	}
400
-
401
-	/**
402
-	 * Returns the request body content.
403
-	 *
404
-	 * If the HTTP request method is PUT and the body
405
-	 * not application/x-www-form-urlencoded or application/json a stream
406
-	 * resource is returned, otherwise an array.
407
-	 *
408
-	 * @return array|string|resource The request body content or a resource to read the body stream.
409
-	 *
410
-	 * @throws \LogicException
411
-	 */
412
-	protected function getContent() {
413
-		// If the content can't be parsed into an array then return a stream resource.
414
-		if ($this->method === 'PUT'
415
-			&& $this->getHeader('Content-Length') !== '0'
416
-			&& $this->getHeader('Content-Length') !== ''
417
-			&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
418
-			&& strpos($this->getHeader('Content-Type'), 'application/json') === false
419
-		) {
420
-			if ($this->content === false) {
421
-				throw new \LogicException(
422
-					'"put" can only be accessed once if not '
423
-					. 'application/x-www-form-urlencoded or application/json.'
424
-				);
425
-			}
426
-			$this->content = false;
427
-			return fopen($this->inputStream, 'rb');
428
-		} else {
429
-			$this->decodeContent();
430
-			return $this->items['parameters'];
431
-		}
432
-	}
433
-
434
-	/**
435
-	 * Attempt to decode the content and populate parameters
436
-	 */
437
-	protected function decodeContent() {
438
-		if ($this->contentDecoded) {
439
-			return;
440
-		}
441
-		$params = [];
442
-
443
-		// 'application/json' must be decoded manually.
444
-		if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
445
-			$params = json_decode(file_get_contents($this->inputStream), true);
446
-			if ($params !== null && \count($params) > 0) {
447
-				$this->items['params'] = $params;
448
-				if ($this->method === 'POST') {
449
-					$this->items['post'] = $params;
450
-				}
451
-			}
452
-
453
-			// Handle application/x-www-form-urlencoded for methods other than GET
454
-		// or post correctly
455
-		} elseif ($this->method !== 'GET'
456
-				&& $this->method !== 'POST'
457
-				&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
458
-			parse_str(file_get_contents($this->inputStream), $params);
459
-			if (\is_array($params)) {
460
-				$this->items['params'] = $params;
461
-			}
462
-		}
463
-
464
-		if (\is_array($params)) {
465
-			$this->items['parameters'] = array_merge($this->items['parameters'], $params);
466
-		}
467
-		$this->contentDecoded = true;
468
-	}
469
-
470
-
471
-	/**
472
-	 * Checks if the CSRF check was correct
473
-	 * @return bool true if CSRF check passed
474
-	 */
475
-	public function passesCSRFCheck(): bool {
476
-		if ($this->csrfTokenManager === null) {
477
-			return false;
478
-		}
479
-
480
-		if (!$this->passesStrictCookieCheck()) {
481
-			return false;
482
-		}
483
-
484
-		if (isset($this->items['get']['requesttoken'])) {
485
-			$token = $this->items['get']['requesttoken'];
486
-		} elseif (isset($this->items['post']['requesttoken'])) {
487
-			$token = $this->items['post']['requesttoken'];
488
-		} elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
489
-			$token = $this->items['server']['HTTP_REQUESTTOKEN'];
490
-		} else {
491
-			//no token found.
492
-			return false;
493
-		}
494
-		$token = new CsrfToken($token);
495
-
496
-		return $this->csrfTokenManager->isTokenValid($token);
497
-	}
498
-
499
-	/**
500
-	 * Whether the cookie checks are required
501
-	 *
502
-	 * @return bool
503
-	 */
504
-	private function cookieCheckRequired(): bool {
505
-		if ($this->getHeader('OCS-APIREQUEST')) {
506
-			return false;
507
-		}
508
-		if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) {
509
-			return false;
510
-		}
511
-
512
-		return true;
513
-	}
514
-
515
-	/**
516
-	 * Wrapper around session_get_cookie_params
517
-	 *
518
-	 * @return array
519
-	 */
520
-	public function getCookieParams(): array {
521
-		return session_get_cookie_params();
522
-	}
523
-
524
-	/**
525
-	 * Appends the __Host- prefix to the cookie if applicable
526
-	 *
527
-	 * @param string $name
528
-	 * @return string
529
-	 */
530
-	protected function getProtectedCookieName(string $name): string {
531
-		$cookieParams = $this->getCookieParams();
532
-		$prefix = '';
533
-		if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
534
-			$prefix = '__Host-';
535
-		}
536
-
537
-		return $prefix.$name;
538
-	}
539
-
540
-	/**
541
-	 * Checks if the strict cookie has been sent with the request if the request
542
-	 * is including any cookies.
543
-	 *
544
-	 * @return bool
545
-	 * @since 9.1.0
546
-	 */
547
-	public function passesStrictCookieCheck(): bool {
548
-		if (!$this->cookieCheckRequired()) {
549
-			return true;
550
-		}
551
-
552
-		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
553
-		if ($this->getCookie($cookieName) === 'true'
554
-			&& $this->passesLaxCookieCheck()) {
555
-			return true;
556
-		}
557
-		return false;
558
-	}
559
-
560
-	/**
561
-	 * Checks if the lax cookie has been sent with the request if the request
562
-	 * is including any cookies.
563
-	 *
564
-	 * @return bool
565
-	 * @since 9.1.0
566
-	 */
567
-	public function passesLaxCookieCheck(): bool {
568
-		if (!$this->cookieCheckRequired()) {
569
-			return true;
570
-		}
571
-
572
-		$cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
573
-		if ($this->getCookie($cookieName) === 'true') {
574
-			return true;
575
-		}
576
-		return false;
577
-	}
578
-
579
-
580
-	/**
581
-	 * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
582
-	 * If `mod_unique_id` is installed this value will be taken.
583
-	 * @return string
584
-	 */
585
-	public function getId(): string {
586
-		if (isset($this->server['UNIQUE_ID'])) {
587
-			return $this->server['UNIQUE_ID'];
588
-		}
589
-
590
-		if (empty($this->requestId)) {
591
-			$validChars = ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS;
592
-			$this->requestId = $this->secureRandom->generate(20, $validChars);
593
-		}
594
-
595
-		return $this->requestId;
596
-	}
597
-
598
-	/**
599
-	 * Checks if given $remoteAddress matches given $trustedProxy.
600
-	 * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if
601
-	 * $remoteAddress is an IPv4 address within that IP range.
602
-	 * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result
603
-	 * will be returned.
604
-	 * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise
605
-	 */
606
-	protected function matchesTrustedProxy($trustedProxy, $remoteAddress) {
607
-		$cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/';
608
-
609
-		if (preg_match($cidrre, $trustedProxy, $match)) {
610
-			$net = $match[1];
611
-			$shiftbits = min(32, max(0, 32 - intval($match[2])));
612
-			$netnum = ip2long($net) >> $shiftbits;
613
-			$ipnum = ip2long($remoteAddress) >> $shiftbits;
614
-
615
-			return $ipnum === $netnum;
616
-		}
617
-
618
-		return $trustedProxy === $remoteAddress;
619
-	}
620
-
621
-	/**
622
-	 * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
623
-	 * For details regarding what "match" means, refer to `matchesTrustedProxy`.
624
-	 * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
625
-	 */
626
-	protected function isTrustedProxy($trustedProxies, $remoteAddress) {
627
-		foreach ($trustedProxies as $tp) {
628
-			if ($this->matchesTrustedProxy($tp, $remoteAddress)) {
629
-				return true;
630
-			}
631
-		}
632
-
633
-		return false;
634
-	}
635
-
636
-	/**
637
-	 * Returns the remote address, if the connection came from a trusted proxy
638
-	 * and `forwarded_for_headers` has been configured then the IP address
639
-	 * specified in this header will be returned instead.
640
-	 * Do always use this instead of $_SERVER['REMOTE_ADDR']
641
-	 * @return string IP address
642
-	 */
643
-	public function getRemoteAddress(): string {
644
-		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
645
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
646
-
647
-		if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
648
-			$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
649
-				'HTTP_X_FORWARDED_FOR'
650
-				// only have one default, so we cannot ship an insecure product out of the box
651
-			]);
652
-
653
-			foreach ($forwardedForHeaders as $header) {
654
-				if (isset($this->server[$header])) {
655
-					foreach (explode(',', $this->server[$header]) as $IP) {
656
-						$IP = trim($IP);
657
-
658
-						// remove brackets from IPv6 addresses
659
-						if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') {
660
-							$IP = substr($IP, 1, -1);
661
-						}
662
-
663
-						if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
664
-							return $IP;
665
-						}
666
-					}
667
-				}
668
-			}
669
-		}
670
-
671
-		return $remoteAddress;
672
-	}
673
-
674
-	/**
675
-	 * Check overwrite condition
676
-	 * @param string $type
677
-	 * @return bool
678
-	 */
679
-	private function isOverwriteCondition(string $type = ''): bool {
680
-		$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
681
-		$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
682
-		return $regex === '//' || preg_match($regex, $remoteAddr) === 1
683
-		|| $type !== 'protocol';
684
-	}
685
-
686
-	/**
687
-	 * Returns the server protocol. It respects one or more reverse proxies servers
688
-	 * and load balancers
689
-	 * @return string Server protocol (http or https)
690
-	 */
691
-	public function getServerProtocol(): string {
692
-		if ($this->config->getSystemValue('overwriteprotocol') !== ''
693
-			&& $this->isOverwriteCondition('protocol')) {
694
-			return $this->config->getSystemValue('overwriteprotocol');
695
-		}
696
-
697
-		if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
698
-			if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
699
-				$parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
700
-				$proto = strtolower(trim($parts[0]));
701
-			} else {
702
-				$proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
703
-			}
704
-
705
-			// Verify that the protocol is always HTTP or HTTPS
706
-			// default to http if an invalid value is provided
707
-			return $proto === 'https' ? 'https' : 'http';
708
-		}
709
-
710
-		if (isset($this->server['HTTPS'])
711
-			&& $this->server['HTTPS'] !== null
712
-			&& $this->server['HTTPS'] !== 'off'
713
-			&& $this->server['HTTPS'] !== '') {
714
-			return 'https';
715
-		}
716
-
717
-		return 'http';
718
-	}
719
-
720
-	/**
721
-	 * Returns the used HTTP protocol.
722
-	 *
723
-	 * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
724
-	 */
725
-	public function getHttpProtocol(): string {
726
-		$claimedProtocol = $this->server['SERVER_PROTOCOL'];
727
-
728
-		if (\is_string($claimedProtocol)) {
729
-			$claimedProtocol = strtoupper($claimedProtocol);
730
-		}
731
-
732
-		$validProtocols = [
733
-			'HTTP/1.0',
734
-			'HTTP/1.1',
735
-			'HTTP/2',
736
-		];
737
-
738
-		if (\in_array($claimedProtocol, $validProtocols, true)) {
739
-			return $claimedProtocol;
740
-		}
741
-
742
-		return 'HTTP/1.1';
743
-	}
744
-
745
-	/**
746
-	 * Returns the request uri, even if the website uses one or more
747
-	 * reverse proxies
748
-	 * @return string
749
-	 */
750
-	public function getRequestUri(): string {
751
-		$uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
752
-		if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
753
-			$uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
754
-		}
755
-		return $uri;
756
-	}
757
-
758
-	/**
759
-	 * Get raw PathInfo from request (not urldecoded)
760
-	 * @throws \Exception
761
-	 * @return string Path info
762
-	 */
763
-	public function getRawPathInfo(): string {
764
-		$requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
765
-		// remove too many slashes - can be caused by reverse proxy configuration
766
-		$requestUri = preg_replace('%/{2,}%', '/', $requestUri);
767
-
768
-		// Remove the query string from REQUEST_URI
769
-		if ($pos = strpos($requestUri, '?')) {
770
-			$requestUri = substr($requestUri, 0, $pos);
771
-		}
772
-
773
-		$scriptName = $this->server['SCRIPT_NAME'];
774
-		$pathInfo = $requestUri;
775
-
776
-		// strip off the script name's dir and file name
777
-		// FIXME: Sabre does not really belong here
778
-		list($path, $name) = \Sabre\Uri\split($scriptName);
779
-		if (!empty($path)) {
780
-			if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
781
-				$pathInfo = substr($pathInfo, \strlen($path));
782
-			} else {
783
-				throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
784
-			}
785
-		}
786
-		if ($name === null) {
787
-			$name = '';
788
-		}
789
-
790
-		if (strpos($pathInfo, '/'.$name) === 0) {
791
-			$pathInfo = substr($pathInfo, \strlen($name) + 1);
792
-		}
793
-		if ($name !== '' && strpos($pathInfo, $name) === 0) {
794
-			$pathInfo = substr($pathInfo, \strlen($name));
795
-		}
796
-		if ($pathInfo === false || $pathInfo === '/') {
797
-			return '';
798
-		} else {
799
-			return $pathInfo;
800
-		}
801
-	}
802
-
803
-	/**
804
-	 * Get PathInfo from request
805
-	 * @throws \Exception
806
-	 * @return string|false Path info or false when not found
807
-	 */
808
-	public function getPathInfo() {
809
-		$pathInfo = $this->getRawPathInfo();
810
-		// following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
811
-		$pathInfo = rawurldecode($pathInfo);
812
-		$encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
813
-
814
-		switch ($encoding) {
815
-			case 'ISO-8859-1':
816
-				$pathInfo = utf8_encode($pathInfo);
817
-		}
818
-		// end copy
819
-
820
-		return $pathInfo;
821
-	}
822
-
823
-	/**
824
-	 * Returns the script name, even if the website uses one or more
825
-	 * reverse proxies
826
-	 * @return string the script name
827
-	 */
828
-	public function getScriptName(): string {
829
-		$name = $this->server['SCRIPT_NAME'];
830
-		$overwriteWebRoot = $this->config->getSystemValue('overwritewebroot');
831
-		if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
832
-			// FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
833
-			$serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
834
-			$suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot)));
835
-			$name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
836
-		}
837
-		return $name;
838
-	}
839
-
840
-	/**
841
-	 * Checks whether the user agent matches a given regex
842
-	 * @param array $agent array of agent names
843
-	 * @return bool true if at least one of the given agent matches, false otherwise
844
-	 */
845
-	public function isUserAgent(array $agent): bool {
846
-		if (!isset($this->server['HTTP_USER_AGENT'])) {
847
-			return false;
848
-		}
849
-		foreach ($agent as $regex) {
850
-			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
851
-				return true;
852
-			}
853
-		}
854
-		return false;
855
-	}
856
-
857
-	/**
858
-	 * Returns the unverified server host from the headers without checking
859
-	 * whether it is a trusted domain
860
-	 * @return string Server host
861
-	 */
862
-	public function getInsecureServerHost(): string {
863
-		if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) {
864
-			return $this->getOverwriteHost();
865
-		}
866
-
867
-		$host = 'localhost';
868
-		if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
869
-			if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
870
-				$parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
871
-				$host = trim(current($parts));
872
-			} else {
873
-				$host = $this->server['HTTP_X_FORWARDED_HOST'];
874
-			}
875
-		} else {
876
-			if (isset($this->server['HTTP_HOST'])) {
877
-				$host = $this->server['HTTP_HOST'];
878
-			} elseif (isset($this->server['SERVER_NAME'])) {
879
-				$host = $this->server['SERVER_NAME'];
880
-			}
881
-		}
882
-
883
-		return $host;
884
-	}
885
-
886
-
887
-	/**
888
-	 * Returns the server host from the headers, or the first configured
889
-	 * trusted domain if the host isn't in the trusted list
890
-	 * @return string Server host
891
-	 */
892
-	public function getServerHost(): string {
893
-		// overwritehost is always trusted
894
-		$host = $this->getOverwriteHost();
895
-		if ($host !== null) {
896
-			return $host;
897
-		}
898
-
899
-		// get the host from the headers
900
-		$host = $this->getInsecureServerHost();
901
-
902
-		// Verify that the host is a trusted domain if the trusted domains
903
-		// are defined
904
-		// If no trusted domain is provided the first trusted domain is returned
905
-		$trustedDomainHelper = new TrustedDomainHelper($this->config);
906
-		if ($trustedDomainHelper->isTrustedDomain($host)) {
907
-			return $host;
908
-		}
909
-
910
-		$trustedList = (array)$this->config->getSystemValue('trusted_domains', []);
911
-		if (count($trustedList) > 0) {
912
-			return reset($trustedList);
913
-		}
914
-
915
-		return '';
916
-	}
917
-
918
-	/**
919
-	 * Returns the overwritehost setting from the config if set and
920
-	 * if the overwrite condition is met
921
-	 * @return string|null overwritehost value or null if not defined or the defined condition
922
-	 * isn't met
923
-	 */
924
-	private function getOverwriteHost() {
925
-		if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
926
-			return $this->config->getSystemValue('overwritehost');
927
-		}
928
-		return null;
929
-	}
930
-
931
-	private function fromTrustedProxy(): bool {
932
-		$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
933
-		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
934
-
935
-		return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress);
936
-	}
66
+    public const USER_AGENT_IE = '/(MSIE)|(Trident)/';
67
+    // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
68
+    public const USER_AGENT_MS_EDGE = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/';
69
+    // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
70
+    public const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/';
71
+    // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
72
+    public const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+( (Vivaldi|Brave|OPR)\/[0-9.]+|)$/';
73
+    // Safari User Agent from http://www.useragentstring.com/pages/Safari/
74
+    public const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/';
75
+    // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
76
+    public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
77
+    public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
78
+    public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
79
+
80
+    /**
81
+     * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_IOS instead
82
+     */
83
+    public const USER_AGENT_OWNCLOUD_IOS = '/^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/';
84
+    /**
85
+     * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_ANDROID instead
86
+     */
87
+    public const USER_AGENT_OWNCLOUD_ANDROID = '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/';
88
+    /**
89
+     * @deprecated use \OCP\IRequest::USER_AGENT_CLIENT_DESKTOP instead
90
+     */
91
+    public const USER_AGENT_OWNCLOUD_DESKTOP = '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/';
92
+
93
+    protected $inputStream;
94
+    protected $content;
95
+    protected $items = [];
96
+    protected $allowedKeys = [
97
+        'get',
98
+        'post',
99
+        'files',
100
+        'server',
101
+        'env',
102
+        'cookies',
103
+        'urlParams',
104
+        'parameters',
105
+        'method',
106
+        'requesttoken',
107
+    ];
108
+    /** @var ISecureRandom */
109
+    protected $secureRandom;
110
+    /** @var IConfig */
111
+    protected $config;
112
+    /** @var string */
113
+    protected $requestId = '';
114
+    /** @var ICrypto */
115
+    protected $crypto;
116
+    /** @var CsrfTokenManager|null */
117
+    protected $csrfTokenManager;
118
+
119
+    /** @var bool */
120
+    protected $contentDecoded = false;
121
+
122
+    /**
123
+     * @param array $vars An associative array with the following optional values:
124
+     *        - array 'urlParams' the parameters which were matched from the URL
125
+     *        - array 'get' the $_GET array
126
+     *        - array|string 'post' the $_POST array or JSON string
127
+     *        - array 'files' the $_FILES array
128
+     *        - array 'server' the $_SERVER array
129
+     *        - array 'env' the $_ENV array
130
+     *        - array 'cookies' the $_COOKIE array
131
+     *        - string 'method' the request method (GET, POST etc)
132
+     *        - string|false 'requesttoken' the requesttoken or false when not available
133
+     * @param ISecureRandom $secureRandom
134
+     * @param IConfig $config
135
+     * @param CsrfTokenManager|null $csrfTokenManager
136
+     * @param string $stream
137
+     * @see https://www.php.net/manual/en/reserved.variables.php
138
+     */
139
+    public function __construct(array $vars,
140
+                                ISecureRandom $secureRandom,
141
+                                IConfig $config,
142
+                                CsrfTokenManager $csrfTokenManager = null,
143
+                                string $stream = 'php://input') {
144
+        $this->inputStream = $stream;
145
+        $this->items['params'] = [];
146
+        $this->secureRandom = $secureRandom;
147
+        $this->config = $config;
148
+        $this->csrfTokenManager = $csrfTokenManager;
149
+
150
+        if (!array_key_exists('method', $vars)) {
151
+            $vars['method'] = 'GET';
152
+        }
153
+
154
+        foreach ($this->allowedKeys as $name) {
155
+            $this->items[$name] = isset($vars[$name])
156
+                ? $vars[$name]
157
+                : [];
158
+        }
159
+
160
+        $this->items['parameters'] = array_merge(
161
+            $this->items['get'],
162
+            $this->items['post'],
163
+            $this->items['urlParams'],
164
+            $this->items['params']
165
+        );
166
+    }
167
+    /**
168
+     * @param array $parameters
169
+     */
170
+    public function setUrlParameters(array $parameters) {
171
+        $this->items['urlParams'] = $parameters;
172
+        $this->items['parameters'] = array_merge(
173
+            $this->items['parameters'],
174
+            $this->items['urlParams']
175
+        );
176
+    }
177
+
178
+    /**
179
+     * Countable method
180
+     * @return int
181
+     */
182
+    public function count(): int {
183
+        return \count($this->items['parameters']);
184
+    }
185
+
186
+    /**
187
+     * ArrayAccess methods
188
+     *
189
+     * Gives access to the combined GET, POST and urlParams arrays
190
+     *
191
+     * Examples:
192
+     *
193
+     * $var = $request['myvar'];
194
+     *
195
+     * or
196
+     *
197
+     * if(!isset($request['myvar']) {
198
+     * 	// Do something
199
+     * }
200
+     *
201
+     * $request['myvar'] = 'something'; // This throws an exception.
202
+     *
203
+     * @param string $offset The key to lookup
204
+     * @return boolean
205
+     */
206
+    public function offsetExists($offset): bool {
207
+        return isset($this->items['parameters'][$offset]);
208
+    }
209
+
210
+    /**
211
+     * @see offsetExists
212
+     * @param string $offset
213
+     * @return mixed
214
+     */
215
+    public function offsetGet($offset) {
216
+        return isset($this->items['parameters'][$offset])
217
+            ? $this->items['parameters'][$offset]
218
+            : null;
219
+    }
220
+
221
+    /**
222
+     * @see offsetExists
223
+     * @param string $offset
224
+     * @param mixed $value
225
+     */
226
+    public function offsetSet($offset, $value) {
227
+        throw new \RuntimeException('You cannot change the contents of the request object');
228
+    }
229
+
230
+    /**
231
+     * @see offsetExists
232
+     * @param string $offset
233
+     */
234
+    public function offsetUnset($offset) {
235
+        throw new \RuntimeException('You cannot change the contents of the request object');
236
+    }
237
+
238
+    /**
239
+     * Magic property accessors
240
+     * @param string $name
241
+     * @param mixed $value
242
+     */
243
+    public function __set($name, $value) {
244
+        throw new \RuntimeException('You cannot change the contents of the request object');
245
+    }
246
+
247
+    /**
248
+     * Access request variables by method and name.
249
+     * Examples:
250
+     *
251
+     * $request->post['myvar']; // Only look for POST variables
252
+     * $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
253
+     * Looks in the combined GET, POST and urlParams array.
254
+     *
255
+     * If you access e.g. ->post but the current HTTP request method
256
+     * is GET a \LogicException will be thrown.
257
+     *
258
+     * @param string $name The key to look for.
259
+     * @throws \LogicException
260
+     * @return mixed|null
261
+     */
262
+    public function __get($name) {
263
+        switch ($name) {
264
+            case 'put':
265
+            case 'patch':
266
+            case 'get':
267
+            case 'post':
268
+                if ($this->method !== strtoupper($name)) {
269
+                    throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
270
+                }
271
+                return $this->getContent();
272
+            case 'files':
273
+            case 'server':
274
+            case 'env':
275
+            case 'cookies':
276
+            case 'urlParams':
277
+            case 'method':
278
+                return isset($this->items[$name])
279
+                    ? $this->items[$name]
280
+                    : null;
281
+            case 'parameters':
282
+            case 'params':
283
+                return $this->getContent();
284
+            default:
285
+                return isset($this[$name])
286
+                    ? $this[$name]
287
+                    : null;
288
+        }
289
+    }
290
+
291
+    /**
292
+     * @param string $name
293
+     * @return bool
294
+     */
295
+    public function __isset($name) {
296
+        if (\in_array($name, $this->allowedKeys, true)) {
297
+            return true;
298
+        }
299
+        return isset($this->items['parameters'][$name]);
300
+    }
301
+
302
+    /**
303
+     * @param string $id
304
+     */
305
+    public function __unset($id) {
306
+        throw new \RuntimeException('You cannot change the contents of the request object');
307
+    }
308
+
309
+    /**
310
+     * Returns the value for a specific http header.
311
+     *
312
+     * This method returns an empty string if the header did not exist.
313
+     *
314
+     * @param string $name
315
+     * @return string
316
+     */
317
+    public function getHeader(string $name): string {
318
+        $name = strtoupper(str_replace('-', '_',$name));
319
+        if (isset($this->server['HTTP_' . $name])) {
320
+            return $this->server['HTTP_' . $name];
321
+        }
322
+
323
+        // There's a few headers that seem to end up in the top-level
324
+        // server array.
325
+        switch ($name) {
326
+            case 'CONTENT_TYPE':
327
+            case 'CONTENT_LENGTH':
328
+            case 'REMOTE_ADDR':
329
+                if (isset($this->server[$name])) {
330
+                    return $this->server[$name];
331
+                }
332
+                break;
333
+        }
334
+
335
+        return '';
336
+    }
337
+
338
+    /**
339
+     * Lets you access post and get parameters by the index
340
+     * In case of json requests the encoded json body is accessed
341
+     *
342
+     * @param string $key the key which you want to access in the URL Parameter
343
+     *                     placeholder, $_POST or $_GET array.
344
+     *                     The priority how they're returned is the following:
345
+     *                     1. URL parameters
346
+     *                     2. POST parameters
347
+     *                     3. GET parameters
348
+     * @param mixed $default If the key is not found, this value will be returned
349
+     * @return mixed the content of the array
350
+     */
351
+    public function getParam(string $key, $default = null) {
352
+        return isset($this->parameters[$key])
353
+            ? $this->parameters[$key]
354
+            : $default;
355
+    }
356
+
357
+    /**
358
+     * Returns all params that were received, be it from the request
359
+     * (as GET or POST) or throuh the URL by the route
360
+     * @return array the array with all parameters
361
+     */
362
+    public function getParams(): array {
363
+        return is_array($this->parameters) ? $this->parameters : [];
364
+    }
365
+
366
+    /**
367
+     * Returns the method of the request
368
+     * @return string the method of the request (POST, GET, etc)
369
+     */
370
+    public function getMethod(): string {
371
+        return $this->method;
372
+    }
373
+
374
+    /**
375
+     * Shortcut for accessing an uploaded file through the $_FILES array
376
+     * @param string $key the key that will be taken from the $_FILES array
377
+     * @return array the file in the $_FILES element
378
+     */
379
+    public function getUploadedFile(string $key) {
380
+        return isset($this->files[$key]) ? $this->files[$key] : null;
381
+    }
382
+
383
+    /**
384
+     * Shortcut for getting env variables
385
+     * @param string $key the key that will be taken from the $_ENV array
386
+     * @return array the value in the $_ENV element
387
+     */
388
+    public function getEnv(string $key) {
389
+        return isset($this->env[$key]) ? $this->env[$key] : null;
390
+    }
391
+
392
+    /**
393
+     * Shortcut for getting cookie variables
394
+     * @param string $key the key that will be taken from the $_COOKIE array
395
+     * @return string the value in the $_COOKIE element
396
+     */
397
+    public function getCookie(string $key) {
398
+        return isset($this->cookies[$key]) ? $this->cookies[$key] : null;
399
+    }
400
+
401
+    /**
402
+     * Returns the request body content.
403
+     *
404
+     * If the HTTP request method is PUT and the body
405
+     * not application/x-www-form-urlencoded or application/json a stream
406
+     * resource is returned, otherwise an array.
407
+     *
408
+     * @return array|string|resource The request body content or a resource to read the body stream.
409
+     *
410
+     * @throws \LogicException
411
+     */
412
+    protected function getContent() {
413
+        // If the content can't be parsed into an array then return a stream resource.
414
+        if ($this->method === 'PUT'
415
+            && $this->getHeader('Content-Length') !== '0'
416
+            && $this->getHeader('Content-Length') !== ''
417
+            && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
418
+            && strpos($this->getHeader('Content-Type'), 'application/json') === false
419
+        ) {
420
+            if ($this->content === false) {
421
+                throw new \LogicException(
422
+                    '"put" can only be accessed once if not '
423
+                    . 'application/x-www-form-urlencoded or application/json.'
424
+                );
425
+            }
426
+            $this->content = false;
427
+            return fopen($this->inputStream, 'rb');
428
+        } else {
429
+            $this->decodeContent();
430
+            return $this->items['parameters'];
431
+        }
432
+    }
433
+
434
+    /**
435
+     * Attempt to decode the content and populate parameters
436
+     */
437
+    protected function decodeContent() {
438
+        if ($this->contentDecoded) {
439
+            return;
440
+        }
441
+        $params = [];
442
+
443
+        // 'application/json' must be decoded manually.
444
+        if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
445
+            $params = json_decode(file_get_contents($this->inputStream), true);
446
+            if ($params !== null && \count($params) > 0) {
447
+                $this->items['params'] = $params;
448
+                if ($this->method === 'POST') {
449
+                    $this->items['post'] = $params;
450
+                }
451
+            }
452
+
453
+            // Handle application/x-www-form-urlencoded for methods other than GET
454
+        // or post correctly
455
+        } elseif ($this->method !== 'GET'
456
+                && $this->method !== 'POST'
457
+                && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
458
+            parse_str(file_get_contents($this->inputStream), $params);
459
+            if (\is_array($params)) {
460
+                $this->items['params'] = $params;
461
+            }
462
+        }
463
+
464
+        if (\is_array($params)) {
465
+            $this->items['parameters'] = array_merge($this->items['parameters'], $params);
466
+        }
467
+        $this->contentDecoded = true;
468
+    }
469
+
470
+
471
+    /**
472
+     * Checks if the CSRF check was correct
473
+     * @return bool true if CSRF check passed
474
+     */
475
+    public function passesCSRFCheck(): bool {
476
+        if ($this->csrfTokenManager === null) {
477
+            return false;
478
+        }
479
+
480
+        if (!$this->passesStrictCookieCheck()) {
481
+            return false;
482
+        }
483
+
484
+        if (isset($this->items['get']['requesttoken'])) {
485
+            $token = $this->items['get']['requesttoken'];
486
+        } elseif (isset($this->items['post']['requesttoken'])) {
487
+            $token = $this->items['post']['requesttoken'];
488
+        } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
489
+            $token = $this->items['server']['HTTP_REQUESTTOKEN'];
490
+        } else {
491
+            //no token found.
492
+            return false;
493
+        }
494
+        $token = new CsrfToken($token);
495
+
496
+        return $this->csrfTokenManager->isTokenValid($token);
497
+    }
498
+
499
+    /**
500
+     * Whether the cookie checks are required
501
+     *
502
+     * @return bool
503
+     */
504
+    private function cookieCheckRequired(): bool {
505
+        if ($this->getHeader('OCS-APIREQUEST')) {
506
+            return false;
507
+        }
508
+        if ($this->getCookie(session_name()) === null && $this->getCookie('nc_token') === null) {
509
+            return false;
510
+        }
511
+
512
+        return true;
513
+    }
514
+
515
+    /**
516
+     * Wrapper around session_get_cookie_params
517
+     *
518
+     * @return array
519
+     */
520
+    public function getCookieParams(): array {
521
+        return session_get_cookie_params();
522
+    }
523
+
524
+    /**
525
+     * Appends the __Host- prefix to the cookie if applicable
526
+     *
527
+     * @param string $name
528
+     * @return string
529
+     */
530
+    protected function getProtectedCookieName(string $name): string {
531
+        $cookieParams = $this->getCookieParams();
532
+        $prefix = '';
533
+        if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
534
+            $prefix = '__Host-';
535
+        }
536
+
537
+        return $prefix.$name;
538
+    }
539
+
540
+    /**
541
+     * Checks if the strict cookie has been sent with the request if the request
542
+     * is including any cookies.
543
+     *
544
+     * @return bool
545
+     * @since 9.1.0
546
+     */
547
+    public function passesStrictCookieCheck(): bool {
548
+        if (!$this->cookieCheckRequired()) {
549
+            return true;
550
+        }
551
+
552
+        $cookieName = $this->getProtectedCookieName('nc_sameSiteCookiestrict');
553
+        if ($this->getCookie($cookieName) === 'true'
554
+            && $this->passesLaxCookieCheck()) {
555
+            return true;
556
+        }
557
+        return false;
558
+    }
559
+
560
+    /**
561
+     * Checks if the lax cookie has been sent with the request if the request
562
+     * is including any cookies.
563
+     *
564
+     * @return bool
565
+     * @since 9.1.0
566
+     */
567
+    public function passesLaxCookieCheck(): bool {
568
+        if (!$this->cookieCheckRequired()) {
569
+            return true;
570
+        }
571
+
572
+        $cookieName = $this->getProtectedCookieName('nc_sameSiteCookielax');
573
+        if ($this->getCookie($cookieName) === 'true') {
574
+            return true;
575
+        }
576
+        return false;
577
+    }
578
+
579
+
580
+    /**
581
+     * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
582
+     * If `mod_unique_id` is installed this value will be taken.
583
+     * @return string
584
+     */
585
+    public function getId(): string {
586
+        if (isset($this->server['UNIQUE_ID'])) {
587
+            return $this->server['UNIQUE_ID'];
588
+        }
589
+
590
+        if (empty($this->requestId)) {
591
+            $validChars = ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS;
592
+            $this->requestId = $this->secureRandom->generate(20, $validChars);
593
+        }
594
+
595
+        return $this->requestId;
596
+    }
597
+
598
+    /**
599
+     * Checks if given $remoteAddress matches given $trustedProxy.
600
+     * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if
601
+     * $remoteAddress is an IPv4 address within that IP range.
602
+     * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result
603
+     * will be returned.
604
+     * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise
605
+     */
606
+    protected function matchesTrustedProxy($trustedProxy, $remoteAddress) {
607
+        $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/';
608
+
609
+        if (preg_match($cidrre, $trustedProxy, $match)) {
610
+            $net = $match[1];
611
+            $shiftbits = min(32, max(0, 32 - intval($match[2])));
612
+            $netnum = ip2long($net) >> $shiftbits;
613
+            $ipnum = ip2long($remoteAddress) >> $shiftbits;
614
+
615
+            return $ipnum === $netnum;
616
+        }
617
+
618
+        return $trustedProxy === $remoteAddress;
619
+    }
620
+
621
+    /**
622
+     * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
623
+     * For details regarding what "match" means, refer to `matchesTrustedProxy`.
624
+     * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
625
+     */
626
+    protected function isTrustedProxy($trustedProxies, $remoteAddress) {
627
+        foreach ($trustedProxies as $tp) {
628
+            if ($this->matchesTrustedProxy($tp, $remoteAddress)) {
629
+                return true;
630
+            }
631
+        }
632
+
633
+        return false;
634
+    }
635
+
636
+    /**
637
+     * Returns the remote address, if the connection came from a trusted proxy
638
+     * and `forwarded_for_headers` has been configured then the IP address
639
+     * specified in this header will be returned instead.
640
+     * Do always use this instead of $_SERVER['REMOTE_ADDR']
641
+     * @return string IP address
642
+     */
643
+    public function getRemoteAddress(): string {
644
+        $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
645
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
646
+
647
+        if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
648
+            $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
649
+                'HTTP_X_FORWARDED_FOR'
650
+                // only have one default, so we cannot ship an insecure product out of the box
651
+            ]);
652
+
653
+            foreach ($forwardedForHeaders as $header) {
654
+                if (isset($this->server[$header])) {
655
+                    foreach (explode(',', $this->server[$header]) as $IP) {
656
+                        $IP = trim($IP);
657
+
658
+                        // remove brackets from IPv6 addresses
659
+                        if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') {
660
+                            $IP = substr($IP, 1, -1);
661
+                        }
662
+
663
+                        if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
664
+                            return $IP;
665
+                        }
666
+                    }
667
+                }
668
+            }
669
+        }
670
+
671
+        return $remoteAddress;
672
+    }
673
+
674
+    /**
675
+     * Check overwrite condition
676
+     * @param string $type
677
+     * @return bool
678
+     */
679
+    private function isOverwriteCondition(string $type = ''): bool {
680
+        $regex = '/' . $this->config->getSystemValue('overwritecondaddr', '')  . '/';
681
+        $remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
682
+        return $regex === '//' || preg_match($regex, $remoteAddr) === 1
683
+        || $type !== 'protocol';
684
+    }
685
+
686
+    /**
687
+     * Returns the server protocol. It respects one or more reverse proxies servers
688
+     * and load balancers
689
+     * @return string Server protocol (http or https)
690
+     */
691
+    public function getServerProtocol(): string {
692
+        if ($this->config->getSystemValue('overwriteprotocol') !== ''
693
+            && $this->isOverwriteCondition('protocol')) {
694
+            return $this->config->getSystemValue('overwriteprotocol');
695
+        }
696
+
697
+        if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) {
698
+            if (strpos($this->server['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
699
+                $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']);
700
+                $proto = strtolower(trim($parts[0]));
701
+            } else {
702
+                $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']);
703
+            }
704
+
705
+            // Verify that the protocol is always HTTP or HTTPS
706
+            // default to http if an invalid value is provided
707
+            return $proto === 'https' ? 'https' : 'http';
708
+        }
709
+
710
+        if (isset($this->server['HTTPS'])
711
+            && $this->server['HTTPS'] !== null
712
+            && $this->server['HTTPS'] !== 'off'
713
+            && $this->server['HTTPS'] !== '') {
714
+            return 'https';
715
+        }
716
+
717
+        return 'http';
718
+    }
719
+
720
+    /**
721
+     * Returns the used HTTP protocol.
722
+     *
723
+     * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
724
+     */
725
+    public function getHttpProtocol(): string {
726
+        $claimedProtocol = $this->server['SERVER_PROTOCOL'];
727
+
728
+        if (\is_string($claimedProtocol)) {
729
+            $claimedProtocol = strtoupper($claimedProtocol);
730
+        }
731
+
732
+        $validProtocols = [
733
+            'HTTP/1.0',
734
+            'HTTP/1.1',
735
+            'HTTP/2',
736
+        ];
737
+
738
+        if (\in_array($claimedProtocol, $validProtocols, true)) {
739
+            return $claimedProtocol;
740
+        }
741
+
742
+        return 'HTTP/1.1';
743
+    }
744
+
745
+    /**
746
+     * Returns the request uri, even if the website uses one or more
747
+     * reverse proxies
748
+     * @return string
749
+     */
750
+    public function getRequestUri(): string {
751
+        $uri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
752
+        if ($this->config->getSystemValue('overwritewebroot') !== '' && $this->isOverwriteCondition()) {
753
+            $uri = $this->getScriptName() . substr($uri, \strlen($this->server['SCRIPT_NAME']));
754
+        }
755
+        return $uri;
756
+    }
757
+
758
+    /**
759
+     * Get raw PathInfo from request (not urldecoded)
760
+     * @throws \Exception
761
+     * @return string Path info
762
+     */
763
+    public function getRawPathInfo(): string {
764
+        $requestUri = isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : '';
765
+        // remove too many slashes - can be caused by reverse proxy configuration
766
+        $requestUri = preg_replace('%/{2,}%', '/', $requestUri);
767
+
768
+        // Remove the query string from REQUEST_URI
769
+        if ($pos = strpos($requestUri, '?')) {
770
+            $requestUri = substr($requestUri, 0, $pos);
771
+        }
772
+
773
+        $scriptName = $this->server['SCRIPT_NAME'];
774
+        $pathInfo = $requestUri;
775
+
776
+        // strip off the script name's dir and file name
777
+        // FIXME: Sabre does not really belong here
778
+        list($path, $name) = \Sabre\Uri\split($scriptName);
779
+        if (!empty($path)) {
780
+            if ($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) {
781
+                $pathInfo = substr($pathInfo, \strlen($path));
782
+            } else {
783
+                throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')");
784
+            }
785
+        }
786
+        if ($name === null) {
787
+            $name = '';
788
+        }
789
+
790
+        if (strpos($pathInfo, '/'.$name) === 0) {
791
+            $pathInfo = substr($pathInfo, \strlen($name) + 1);
792
+        }
793
+        if ($name !== '' && strpos($pathInfo, $name) === 0) {
794
+            $pathInfo = substr($pathInfo, \strlen($name));
795
+        }
796
+        if ($pathInfo === false || $pathInfo === '/') {
797
+            return '';
798
+        } else {
799
+            return $pathInfo;
800
+        }
801
+    }
802
+
803
+    /**
804
+     * Get PathInfo from request
805
+     * @throws \Exception
806
+     * @return string|false Path info or false when not found
807
+     */
808
+    public function getPathInfo() {
809
+        $pathInfo = $this->getRawPathInfo();
810
+        // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment
811
+        $pathInfo = rawurldecode($pathInfo);
812
+        $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']);
813
+
814
+        switch ($encoding) {
815
+            case 'ISO-8859-1':
816
+                $pathInfo = utf8_encode($pathInfo);
817
+        }
818
+        // end copy
819
+
820
+        return $pathInfo;
821
+    }
822
+
823
+    /**
824
+     * Returns the script name, even if the website uses one or more
825
+     * reverse proxies
826
+     * @return string the script name
827
+     */
828
+    public function getScriptName(): string {
829
+        $name = $this->server['SCRIPT_NAME'];
830
+        $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot');
831
+        if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) {
832
+            // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous
833
+            $serverRoot = str_replace('\\', '/', substr(__DIR__, 0, -\strlen('lib/private/appframework/http/')));
834
+            $suburi = str_replace('\\', '/', substr(realpath($this->server['SCRIPT_FILENAME']), \strlen($serverRoot)));
835
+            $name = '/' . ltrim($overwriteWebRoot . $suburi, '/');
836
+        }
837
+        return $name;
838
+    }
839
+
840
+    /**
841
+     * Checks whether the user agent matches a given regex
842
+     * @param array $agent array of agent names
843
+     * @return bool true if at least one of the given agent matches, false otherwise
844
+     */
845
+    public function isUserAgent(array $agent): bool {
846
+        if (!isset($this->server['HTTP_USER_AGENT'])) {
847
+            return false;
848
+        }
849
+        foreach ($agent as $regex) {
850
+            if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
851
+                return true;
852
+            }
853
+        }
854
+        return false;
855
+    }
856
+
857
+    /**
858
+     * Returns the unverified server host from the headers without checking
859
+     * whether it is a trusted domain
860
+     * @return string Server host
861
+     */
862
+    public function getInsecureServerHost(): string {
863
+        if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) {
864
+            return $this->getOverwriteHost();
865
+        }
866
+
867
+        $host = 'localhost';
868
+        if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) {
869
+            if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) {
870
+                $parts = explode(',', $this->server['HTTP_X_FORWARDED_HOST']);
871
+                $host = trim(current($parts));
872
+            } else {
873
+                $host = $this->server['HTTP_X_FORWARDED_HOST'];
874
+            }
875
+        } else {
876
+            if (isset($this->server['HTTP_HOST'])) {
877
+                $host = $this->server['HTTP_HOST'];
878
+            } elseif (isset($this->server['SERVER_NAME'])) {
879
+                $host = $this->server['SERVER_NAME'];
880
+            }
881
+        }
882
+
883
+        return $host;
884
+    }
885
+
886
+
887
+    /**
888
+     * Returns the server host from the headers, or the first configured
889
+     * trusted domain if the host isn't in the trusted list
890
+     * @return string Server host
891
+     */
892
+    public function getServerHost(): string {
893
+        // overwritehost is always trusted
894
+        $host = $this->getOverwriteHost();
895
+        if ($host !== null) {
896
+            return $host;
897
+        }
898
+
899
+        // get the host from the headers
900
+        $host = $this->getInsecureServerHost();
901
+
902
+        // Verify that the host is a trusted domain if the trusted domains
903
+        // are defined
904
+        // If no trusted domain is provided the first trusted domain is returned
905
+        $trustedDomainHelper = new TrustedDomainHelper($this->config);
906
+        if ($trustedDomainHelper->isTrustedDomain($host)) {
907
+            return $host;
908
+        }
909
+
910
+        $trustedList = (array)$this->config->getSystemValue('trusted_domains', []);
911
+        if (count($trustedList) > 0) {
912
+            return reset($trustedList);
913
+        }
914
+
915
+        return '';
916
+    }
917
+
918
+    /**
919
+     * Returns the overwritehost setting from the config if set and
920
+     * if the overwrite condition is met
921
+     * @return string|null overwritehost value or null if not defined or the defined condition
922
+     * isn't met
923
+     */
924
+    private function getOverwriteHost() {
925
+        if ($this->config->getSystemValue('overwritehost') !== '' && $this->isOverwriteCondition()) {
926
+            return $this->config->getSystemValue('overwritehost');
927
+        }
928
+        return null;
929
+    }
930
+
931
+    private function fromTrustedProxy(): bool {
932
+        $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
933
+        $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
934
+
935
+        return \is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress);
936
+    }
937 937
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Wrapper.php 1 patch
Indentation   +604 added lines, -604 removed lines patch added patch discarded remove patch
@@ -37,608 +37,608 @@
 block discarded – undo
37 37
 use OCP\Lock\ILockingProvider;
38 38
 
39 39
 class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage {
40
-	/**
41
-	 * @var \OC\Files\Storage\Storage $storage
42
-	 */
43
-	protected $storage;
44
-
45
-	public $cache;
46
-	public $scanner;
47
-	public $watcher;
48
-	public $propagator;
49
-	public $updater;
50
-
51
-	/**
52
-	 * @param array $parameters
53
-	 */
54
-	public function __construct($parameters) {
55
-		$this->storage = $parameters['storage'];
56
-	}
57
-
58
-	/**
59
-	 * @return \OC\Files\Storage\Storage
60
-	 */
61
-	public function getWrapperStorage() {
62
-		return $this->storage;
63
-	}
64
-
65
-	/**
66
-	 * Get the identifier for the storage,
67
-	 * the returned id should be the same for every storage object that is created with the same parameters
68
-	 * and two storage objects with the same id should refer to two storages that display the same files.
69
-	 *
70
-	 * @return string
71
-	 */
72
-	public function getId() {
73
-		return $this->getWrapperStorage()->getId();
74
-	}
75
-
76
-	/**
77
-	 * see https://www.php.net/manual/en/function.mkdir.php
78
-	 *
79
-	 * @param string $path
80
-	 * @return bool
81
-	 */
82
-	public function mkdir($path) {
83
-		return $this->getWrapperStorage()->mkdir($path);
84
-	}
85
-
86
-	/**
87
-	 * see https://www.php.net/manual/en/function.rmdir.php
88
-	 *
89
-	 * @param string $path
90
-	 * @return bool
91
-	 */
92
-	public function rmdir($path) {
93
-		return $this->getWrapperStorage()->rmdir($path);
94
-	}
95
-
96
-	/**
97
-	 * see https://www.php.net/manual/en/function.opendir.php
98
-	 *
99
-	 * @param string $path
100
-	 * @return resource
101
-	 */
102
-	public function opendir($path) {
103
-		return $this->getWrapperStorage()->opendir($path);
104
-	}
105
-
106
-	/**
107
-	 * see https://www.php.net/manual/en/function.is_dir.php
108
-	 *
109
-	 * @param string $path
110
-	 * @return bool
111
-	 */
112
-	public function is_dir($path) {
113
-		return $this->getWrapperStorage()->is_dir($path);
114
-	}
115
-
116
-	/**
117
-	 * see https://www.php.net/manual/en/function.is_file.php
118
-	 *
119
-	 * @param string $path
120
-	 * @return bool
121
-	 */
122
-	public function is_file($path) {
123
-		return $this->getWrapperStorage()->is_file($path);
124
-	}
125
-
126
-	/**
127
-	 * see https://www.php.net/manual/en/function.stat.php
128
-	 * only the following keys are required in the result: size and mtime
129
-	 *
130
-	 * @param string $path
131
-	 * @return array
132
-	 */
133
-	public function stat($path) {
134
-		return $this->getWrapperStorage()->stat($path);
135
-	}
136
-
137
-	/**
138
-	 * see https://www.php.net/manual/en/function.filetype.php
139
-	 *
140
-	 * @param string $path
141
-	 * @return bool
142
-	 */
143
-	public function filetype($path) {
144
-		return $this->getWrapperStorage()->filetype($path);
145
-	}
146
-
147
-	/**
148
-	 * see https://www.php.net/manual/en/function.filesize.php
149
-	 * The result for filesize when called on a folder is required to be 0
150
-	 *
151
-	 * @param string $path
152
-	 * @return int
153
-	 */
154
-	public function filesize($path) {
155
-		return $this->getWrapperStorage()->filesize($path);
156
-	}
157
-
158
-	/**
159
-	 * check if a file can be created in $path
160
-	 *
161
-	 * @param string $path
162
-	 * @return bool
163
-	 */
164
-	public function isCreatable($path) {
165
-		return $this->getWrapperStorage()->isCreatable($path);
166
-	}
167
-
168
-	/**
169
-	 * check if a file can be read
170
-	 *
171
-	 * @param string $path
172
-	 * @return bool
173
-	 */
174
-	public function isReadable($path) {
175
-		return $this->getWrapperStorage()->isReadable($path);
176
-	}
177
-
178
-	/**
179
-	 * check if a file can be written to
180
-	 *
181
-	 * @param string $path
182
-	 * @return bool
183
-	 */
184
-	public function isUpdatable($path) {
185
-		return $this->getWrapperStorage()->isUpdatable($path);
186
-	}
187
-
188
-	/**
189
-	 * check if a file can be deleted
190
-	 *
191
-	 * @param string $path
192
-	 * @return bool
193
-	 */
194
-	public function isDeletable($path) {
195
-		return $this->getWrapperStorage()->isDeletable($path);
196
-	}
197
-
198
-	/**
199
-	 * check if a file can be shared
200
-	 *
201
-	 * @param string $path
202
-	 * @return bool
203
-	 */
204
-	public function isSharable($path) {
205
-		return $this->getWrapperStorage()->isSharable($path);
206
-	}
207
-
208
-	/**
209
-	 * get the full permissions of a path.
210
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
211
-	 *
212
-	 * @param string $path
213
-	 * @return int
214
-	 */
215
-	public function getPermissions($path) {
216
-		return $this->getWrapperStorage()->getPermissions($path);
217
-	}
218
-
219
-	/**
220
-	 * see https://www.php.net/manual/en/function.file_exists.php
221
-	 *
222
-	 * @param string $path
223
-	 * @return bool
224
-	 */
225
-	public function file_exists($path) {
226
-		return $this->getWrapperStorage()->file_exists($path);
227
-	}
228
-
229
-	/**
230
-	 * see https://www.php.net/manual/en/function.filemtime.php
231
-	 *
232
-	 * @param string $path
233
-	 * @return int
234
-	 */
235
-	public function filemtime($path) {
236
-		return $this->getWrapperStorage()->filemtime($path);
237
-	}
238
-
239
-	/**
240
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
241
-	 *
242
-	 * @param string $path
243
-	 * @return string
244
-	 */
245
-	public function file_get_contents($path) {
246
-		return $this->getWrapperStorage()->file_get_contents($path);
247
-	}
248
-
249
-	/**
250
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
251
-	 *
252
-	 * @param string $path
253
-	 * @param mixed $data
254
-	 * @return int|false
255
-	 */
256
-	public function file_put_contents($path, $data) {
257
-		return $this->getWrapperStorage()->file_put_contents($path, $data);
258
-	}
259
-
260
-	/**
261
-	 * see https://www.php.net/manual/en/function.unlink.php
262
-	 *
263
-	 * @param string $path
264
-	 * @return bool
265
-	 */
266
-	public function unlink($path) {
267
-		return $this->getWrapperStorage()->unlink($path);
268
-	}
269
-
270
-	/**
271
-	 * see https://www.php.net/manual/en/function.rename.php
272
-	 *
273
-	 * @param string $path1
274
-	 * @param string $path2
275
-	 * @return bool
276
-	 */
277
-	public function rename($path1, $path2) {
278
-		return $this->getWrapperStorage()->rename($path1, $path2);
279
-	}
280
-
281
-	/**
282
-	 * see https://www.php.net/manual/en/function.copy.php
283
-	 *
284
-	 * @param string $path1
285
-	 * @param string $path2
286
-	 * @return bool
287
-	 */
288
-	public function copy($path1, $path2) {
289
-		return $this->getWrapperStorage()->copy($path1, $path2);
290
-	}
291
-
292
-	/**
293
-	 * see https://www.php.net/manual/en/function.fopen.php
294
-	 *
295
-	 * @param string $path
296
-	 * @param string $mode
297
-	 * @return resource
298
-	 */
299
-	public function fopen($path, $mode) {
300
-		return $this->getWrapperStorage()->fopen($path, $mode);
301
-	}
302
-
303
-	/**
304
-	 * get the mimetype for a file or folder
305
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
306
-	 *
307
-	 * @param string $path
308
-	 * @return string
309
-	 */
310
-	public function getMimeType($path) {
311
-		return $this->getWrapperStorage()->getMimeType($path);
312
-	}
313
-
314
-	/**
315
-	 * see https://www.php.net/manual/en/function.hash.php
316
-	 *
317
-	 * @param string $type
318
-	 * @param string $path
319
-	 * @param bool $raw
320
-	 * @return string
321
-	 */
322
-	public function hash($type, $path, $raw = false) {
323
-		return $this->getWrapperStorage()->hash($type, $path, $raw);
324
-	}
325
-
326
-	/**
327
-	 * see https://www.php.net/manual/en/function.free_space.php
328
-	 *
329
-	 * @param string $path
330
-	 * @return int
331
-	 */
332
-	public function free_space($path) {
333
-		return $this->getWrapperStorage()->free_space($path);
334
-	}
335
-
336
-	/**
337
-	 * search for occurrences of $query in file names
338
-	 *
339
-	 * @param string $query
340
-	 * @return array
341
-	 */
342
-	public function search($query) {
343
-		return $this->getWrapperStorage()->search($query);
344
-	}
345
-
346
-	/**
347
-	 * see https://www.php.net/manual/en/function.touch.php
348
-	 * If the backend does not support the operation, false should be returned
349
-	 *
350
-	 * @param string $path
351
-	 * @param int $mtime
352
-	 * @return bool
353
-	 */
354
-	public function touch($path, $mtime = null) {
355
-		return $this->getWrapperStorage()->touch($path, $mtime);
356
-	}
357
-
358
-	/**
359
-	 * get the path to a local version of the file.
360
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
361
-	 *
362
-	 * @param string $path
363
-	 * @return string
364
-	 */
365
-	public function getLocalFile($path) {
366
-		return $this->getWrapperStorage()->getLocalFile($path);
367
-	}
368
-
369
-	/**
370
-	 * check if a file or folder has been updated since $time
371
-	 *
372
-	 * @param string $path
373
-	 * @param int $time
374
-	 * @return bool
375
-	 *
376
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
377
-	 * returning true for other changes in the folder is optional
378
-	 */
379
-	public function hasUpdated($path, $time) {
380
-		return $this->getWrapperStorage()->hasUpdated($path, $time);
381
-	}
382
-
383
-	/**
384
-	 * get a cache instance for the storage
385
-	 *
386
-	 * @param string $path
387
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
388
-	 * @return \OC\Files\Cache\Cache
389
-	 */
390
-	public function getCache($path = '', $storage = null) {
391
-		if (!$storage) {
392
-			$storage = $this;
393
-		}
394
-		return $this->getWrapperStorage()->getCache($path, $storage);
395
-	}
396
-
397
-	/**
398
-	 * get a scanner instance for the storage
399
-	 *
400
-	 * @param string $path
401
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
402
-	 * @return \OC\Files\Cache\Scanner
403
-	 */
404
-	public function getScanner($path = '', $storage = null) {
405
-		if (!$storage) {
406
-			$storage = $this;
407
-		}
408
-		return $this->getWrapperStorage()->getScanner($path, $storage);
409
-	}
410
-
411
-
412
-	/**
413
-	 * get the user id of the owner of a file or folder
414
-	 *
415
-	 * @param string $path
416
-	 * @return string
417
-	 */
418
-	public function getOwner($path) {
419
-		return $this->getWrapperStorage()->getOwner($path);
420
-	}
421
-
422
-	/**
423
-	 * get a watcher instance for the cache
424
-	 *
425
-	 * @param string $path
426
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
427
-	 * @return \OC\Files\Cache\Watcher
428
-	 */
429
-	public function getWatcher($path = '', $storage = null) {
430
-		if (!$storage) {
431
-			$storage = $this;
432
-		}
433
-		return $this->getWrapperStorage()->getWatcher($path, $storage);
434
-	}
435
-
436
-	public function getPropagator($storage = null) {
437
-		if (!$storage) {
438
-			$storage = $this;
439
-		}
440
-		return $this->getWrapperStorage()->getPropagator($storage);
441
-	}
442
-
443
-	public function getUpdater($storage = null) {
444
-		if (!$storage) {
445
-			$storage = $this;
446
-		}
447
-		return $this->getWrapperStorage()->getUpdater($storage);
448
-	}
449
-
450
-	/**
451
-	 * @return \OC\Files\Cache\Storage
452
-	 */
453
-	public function getStorageCache() {
454
-		return $this->getWrapperStorage()->getStorageCache();
455
-	}
456
-
457
-	/**
458
-	 * get the ETag for a file or folder
459
-	 *
460
-	 * @param string $path
461
-	 * @return string
462
-	 */
463
-	public function getETag($path) {
464
-		return $this->getWrapperStorage()->getETag($path);
465
-	}
466
-
467
-	/**
468
-	 * Returns true
469
-	 *
470
-	 * @return true
471
-	 */
472
-	public function test() {
473
-		return $this->getWrapperStorage()->test();
474
-	}
475
-
476
-	/**
477
-	 * Returns the wrapped storage's value for isLocal()
478
-	 *
479
-	 * @return bool wrapped storage's isLocal() value
480
-	 */
481
-	public function isLocal() {
482
-		return $this->getWrapperStorage()->isLocal();
483
-	}
484
-
485
-	/**
486
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
487
-	 *
488
-	 * @param string $class
489
-	 * @return bool
490
-	 */
491
-	public function instanceOfStorage($class) {
492
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
493
-			// FIXME Temporary fix to keep existing checks working
494
-			$class = '\OCA\Files_Sharing\SharedStorage';
495
-		}
496
-		return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
497
-	}
498
-
499
-	/**
500
-	 * Pass any methods custom to specific storage implementations to the wrapped storage
501
-	 *
502
-	 * @param string $method
503
-	 * @param array $args
504
-	 * @return mixed
505
-	 */
506
-	public function __call($method, $args) {
507
-		return call_user_func_array([$this->getWrapperStorage(), $method], $args);
508
-	}
509
-
510
-	/**
511
-	 * A custom storage implementation can return an url for direct download of a give file.
512
-	 *
513
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
514
-	 *
515
-	 * @param string $path
516
-	 * @return array
517
-	 */
518
-	public function getDirectDownload($path) {
519
-		return $this->getWrapperStorage()->getDirectDownload($path);
520
-	}
521
-
522
-	/**
523
-	 * Get availability of the storage
524
-	 *
525
-	 * @return array [ available, last_checked ]
526
-	 */
527
-	public function getAvailability() {
528
-		return $this->getWrapperStorage()->getAvailability();
529
-	}
530
-
531
-	/**
532
-	 * Set availability of the storage
533
-	 *
534
-	 * @param bool $isAvailable
535
-	 */
536
-	public function setAvailability($isAvailable) {
537
-		$this->getWrapperStorage()->setAvailability($isAvailable);
538
-	}
539
-
540
-	/**
541
-	 * @param string $path the path of the target folder
542
-	 * @param string $fileName the name of the file itself
543
-	 * @return void
544
-	 * @throws InvalidPathException
545
-	 */
546
-	public function verifyPath($path, $fileName) {
547
-		$this->getWrapperStorage()->verifyPath($path, $fileName);
548
-	}
549
-
550
-	/**
551
-	 * @param IStorage $sourceStorage
552
-	 * @param string $sourceInternalPath
553
-	 * @param string $targetInternalPath
554
-	 * @return bool
555
-	 */
556
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
557
-		if ($sourceStorage === $this) {
558
-			return $this->copy($sourceInternalPath, $targetInternalPath);
559
-		}
560
-
561
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
562
-	}
563
-
564
-	/**
565
-	 * @param IStorage $sourceStorage
566
-	 * @param string $sourceInternalPath
567
-	 * @param string $targetInternalPath
568
-	 * @return bool
569
-	 */
570
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
571
-		if ($sourceStorage === $this) {
572
-			return $this->rename($sourceInternalPath, $targetInternalPath);
573
-		}
574
-
575
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
576
-	}
577
-
578
-	/**
579
-	 * @param string $path
580
-	 * @return array
581
-	 */
582
-	public function getMetaData($path) {
583
-		return $this->getWrapperStorage()->getMetaData($path);
584
-	}
585
-
586
-	/**
587
-	 * @param string $path
588
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
589
-	 * @param \OCP\Lock\ILockingProvider $provider
590
-	 * @throws \OCP\Lock\LockedException
591
-	 */
592
-	public function acquireLock($path, $type, ILockingProvider $provider) {
593
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
594
-			$this->getWrapperStorage()->acquireLock($path, $type, $provider);
595
-		}
596
-	}
597
-
598
-	/**
599
-	 * @param string $path
600
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
601
-	 * @param \OCP\Lock\ILockingProvider $provider
602
-	 */
603
-	public function releaseLock($path, $type, ILockingProvider $provider) {
604
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
605
-			$this->getWrapperStorage()->releaseLock($path, $type, $provider);
606
-		}
607
-	}
608
-
609
-	/**
610
-	 * @param string $path
611
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
612
-	 * @param \OCP\Lock\ILockingProvider $provider
613
-	 */
614
-	public function changeLock($path, $type, ILockingProvider $provider) {
615
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
616
-			$this->getWrapperStorage()->changeLock($path, $type, $provider);
617
-		}
618
-	}
619
-
620
-	/**
621
-	 * @return bool
622
-	 */
623
-	public function needsPartFile() {
624
-		return $this->getWrapperStorage()->needsPartFile();
625
-	}
626
-
627
-	public function writeStream(string $path, $stream, int $size = null): int {
628
-		$storage = $this->getWrapperStorage();
629
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
630
-			/** @var IWriteStreamStorage $storage */
631
-			return $storage->writeStream($path, $stream, $size);
632
-		} else {
633
-			$target = $this->fopen($path, 'w');
634
-			list($count, $result) = \OC_Helper::streamCopy($stream, $target);
635
-			fclose($stream);
636
-			fclose($target);
637
-			return $count;
638
-		}
639
-	}
640
-
641
-	public function getDirectoryContent($directory): \Traversable {
642
-		return $this->getWrapperStorage()->getDirectoryContent($directory);
643
-	}
40
+    /**
41
+     * @var \OC\Files\Storage\Storage $storage
42
+     */
43
+    protected $storage;
44
+
45
+    public $cache;
46
+    public $scanner;
47
+    public $watcher;
48
+    public $propagator;
49
+    public $updater;
50
+
51
+    /**
52
+     * @param array $parameters
53
+     */
54
+    public function __construct($parameters) {
55
+        $this->storage = $parameters['storage'];
56
+    }
57
+
58
+    /**
59
+     * @return \OC\Files\Storage\Storage
60
+     */
61
+    public function getWrapperStorage() {
62
+        return $this->storage;
63
+    }
64
+
65
+    /**
66
+     * Get the identifier for the storage,
67
+     * the returned id should be the same for every storage object that is created with the same parameters
68
+     * and two storage objects with the same id should refer to two storages that display the same files.
69
+     *
70
+     * @return string
71
+     */
72
+    public function getId() {
73
+        return $this->getWrapperStorage()->getId();
74
+    }
75
+
76
+    /**
77
+     * see https://www.php.net/manual/en/function.mkdir.php
78
+     *
79
+     * @param string $path
80
+     * @return bool
81
+     */
82
+    public function mkdir($path) {
83
+        return $this->getWrapperStorage()->mkdir($path);
84
+    }
85
+
86
+    /**
87
+     * see https://www.php.net/manual/en/function.rmdir.php
88
+     *
89
+     * @param string $path
90
+     * @return bool
91
+     */
92
+    public function rmdir($path) {
93
+        return $this->getWrapperStorage()->rmdir($path);
94
+    }
95
+
96
+    /**
97
+     * see https://www.php.net/manual/en/function.opendir.php
98
+     *
99
+     * @param string $path
100
+     * @return resource
101
+     */
102
+    public function opendir($path) {
103
+        return $this->getWrapperStorage()->opendir($path);
104
+    }
105
+
106
+    /**
107
+     * see https://www.php.net/manual/en/function.is_dir.php
108
+     *
109
+     * @param string $path
110
+     * @return bool
111
+     */
112
+    public function is_dir($path) {
113
+        return $this->getWrapperStorage()->is_dir($path);
114
+    }
115
+
116
+    /**
117
+     * see https://www.php.net/manual/en/function.is_file.php
118
+     *
119
+     * @param string $path
120
+     * @return bool
121
+     */
122
+    public function is_file($path) {
123
+        return $this->getWrapperStorage()->is_file($path);
124
+    }
125
+
126
+    /**
127
+     * see https://www.php.net/manual/en/function.stat.php
128
+     * only the following keys are required in the result: size and mtime
129
+     *
130
+     * @param string $path
131
+     * @return array
132
+     */
133
+    public function stat($path) {
134
+        return $this->getWrapperStorage()->stat($path);
135
+    }
136
+
137
+    /**
138
+     * see https://www.php.net/manual/en/function.filetype.php
139
+     *
140
+     * @param string $path
141
+     * @return bool
142
+     */
143
+    public function filetype($path) {
144
+        return $this->getWrapperStorage()->filetype($path);
145
+    }
146
+
147
+    /**
148
+     * see https://www.php.net/manual/en/function.filesize.php
149
+     * The result for filesize when called on a folder is required to be 0
150
+     *
151
+     * @param string $path
152
+     * @return int
153
+     */
154
+    public function filesize($path) {
155
+        return $this->getWrapperStorage()->filesize($path);
156
+    }
157
+
158
+    /**
159
+     * check if a file can be created in $path
160
+     *
161
+     * @param string $path
162
+     * @return bool
163
+     */
164
+    public function isCreatable($path) {
165
+        return $this->getWrapperStorage()->isCreatable($path);
166
+    }
167
+
168
+    /**
169
+     * check if a file can be read
170
+     *
171
+     * @param string $path
172
+     * @return bool
173
+     */
174
+    public function isReadable($path) {
175
+        return $this->getWrapperStorage()->isReadable($path);
176
+    }
177
+
178
+    /**
179
+     * check if a file can be written to
180
+     *
181
+     * @param string $path
182
+     * @return bool
183
+     */
184
+    public function isUpdatable($path) {
185
+        return $this->getWrapperStorage()->isUpdatable($path);
186
+    }
187
+
188
+    /**
189
+     * check if a file can be deleted
190
+     *
191
+     * @param string $path
192
+     * @return bool
193
+     */
194
+    public function isDeletable($path) {
195
+        return $this->getWrapperStorage()->isDeletable($path);
196
+    }
197
+
198
+    /**
199
+     * check if a file can be shared
200
+     *
201
+     * @param string $path
202
+     * @return bool
203
+     */
204
+    public function isSharable($path) {
205
+        return $this->getWrapperStorage()->isSharable($path);
206
+    }
207
+
208
+    /**
209
+     * get the full permissions of a path.
210
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
211
+     *
212
+     * @param string $path
213
+     * @return int
214
+     */
215
+    public function getPermissions($path) {
216
+        return $this->getWrapperStorage()->getPermissions($path);
217
+    }
218
+
219
+    /**
220
+     * see https://www.php.net/manual/en/function.file_exists.php
221
+     *
222
+     * @param string $path
223
+     * @return bool
224
+     */
225
+    public function file_exists($path) {
226
+        return $this->getWrapperStorage()->file_exists($path);
227
+    }
228
+
229
+    /**
230
+     * see https://www.php.net/manual/en/function.filemtime.php
231
+     *
232
+     * @param string $path
233
+     * @return int
234
+     */
235
+    public function filemtime($path) {
236
+        return $this->getWrapperStorage()->filemtime($path);
237
+    }
238
+
239
+    /**
240
+     * see https://www.php.net/manual/en/function.file_get_contents.php
241
+     *
242
+     * @param string $path
243
+     * @return string
244
+     */
245
+    public function file_get_contents($path) {
246
+        return $this->getWrapperStorage()->file_get_contents($path);
247
+    }
248
+
249
+    /**
250
+     * see https://www.php.net/manual/en/function.file_put_contents.php
251
+     *
252
+     * @param string $path
253
+     * @param mixed $data
254
+     * @return int|false
255
+     */
256
+    public function file_put_contents($path, $data) {
257
+        return $this->getWrapperStorage()->file_put_contents($path, $data);
258
+    }
259
+
260
+    /**
261
+     * see https://www.php.net/manual/en/function.unlink.php
262
+     *
263
+     * @param string $path
264
+     * @return bool
265
+     */
266
+    public function unlink($path) {
267
+        return $this->getWrapperStorage()->unlink($path);
268
+    }
269
+
270
+    /**
271
+     * see https://www.php.net/manual/en/function.rename.php
272
+     *
273
+     * @param string $path1
274
+     * @param string $path2
275
+     * @return bool
276
+     */
277
+    public function rename($path1, $path2) {
278
+        return $this->getWrapperStorage()->rename($path1, $path2);
279
+    }
280
+
281
+    /**
282
+     * see https://www.php.net/manual/en/function.copy.php
283
+     *
284
+     * @param string $path1
285
+     * @param string $path2
286
+     * @return bool
287
+     */
288
+    public function copy($path1, $path2) {
289
+        return $this->getWrapperStorage()->copy($path1, $path2);
290
+    }
291
+
292
+    /**
293
+     * see https://www.php.net/manual/en/function.fopen.php
294
+     *
295
+     * @param string $path
296
+     * @param string $mode
297
+     * @return resource
298
+     */
299
+    public function fopen($path, $mode) {
300
+        return $this->getWrapperStorage()->fopen($path, $mode);
301
+    }
302
+
303
+    /**
304
+     * get the mimetype for a file or folder
305
+     * The mimetype for a folder is required to be "httpd/unix-directory"
306
+     *
307
+     * @param string $path
308
+     * @return string
309
+     */
310
+    public function getMimeType($path) {
311
+        return $this->getWrapperStorage()->getMimeType($path);
312
+    }
313
+
314
+    /**
315
+     * see https://www.php.net/manual/en/function.hash.php
316
+     *
317
+     * @param string $type
318
+     * @param string $path
319
+     * @param bool $raw
320
+     * @return string
321
+     */
322
+    public function hash($type, $path, $raw = false) {
323
+        return $this->getWrapperStorage()->hash($type, $path, $raw);
324
+    }
325
+
326
+    /**
327
+     * see https://www.php.net/manual/en/function.free_space.php
328
+     *
329
+     * @param string $path
330
+     * @return int
331
+     */
332
+    public function free_space($path) {
333
+        return $this->getWrapperStorage()->free_space($path);
334
+    }
335
+
336
+    /**
337
+     * search for occurrences of $query in file names
338
+     *
339
+     * @param string $query
340
+     * @return array
341
+     */
342
+    public function search($query) {
343
+        return $this->getWrapperStorage()->search($query);
344
+    }
345
+
346
+    /**
347
+     * see https://www.php.net/manual/en/function.touch.php
348
+     * If the backend does not support the operation, false should be returned
349
+     *
350
+     * @param string $path
351
+     * @param int $mtime
352
+     * @return bool
353
+     */
354
+    public function touch($path, $mtime = null) {
355
+        return $this->getWrapperStorage()->touch($path, $mtime);
356
+    }
357
+
358
+    /**
359
+     * get the path to a local version of the file.
360
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
361
+     *
362
+     * @param string $path
363
+     * @return string
364
+     */
365
+    public function getLocalFile($path) {
366
+        return $this->getWrapperStorage()->getLocalFile($path);
367
+    }
368
+
369
+    /**
370
+     * check if a file or folder has been updated since $time
371
+     *
372
+     * @param string $path
373
+     * @param int $time
374
+     * @return bool
375
+     *
376
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
377
+     * returning true for other changes in the folder is optional
378
+     */
379
+    public function hasUpdated($path, $time) {
380
+        return $this->getWrapperStorage()->hasUpdated($path, $time);
381
+    }
382
+
383
+    /**
384
+     * get a cache instance for the storage
385
+     *
386
+     * @param string $path
387
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
388
+     * @return \OC\Files\Cache\Cache
389
+     */
390
+    public function getCache($path = '', $storage = null) {
391
+        if (!$storage) {
392
+            $storage = $this;
393
+        }
394
+        return $this->getWrapperStorage()->getCache($path, $storage);
395
+    }
396
+
397
+    /**
398
+     * get a scanner instance for the storage
399
+     *
400
+     * @param string $path
401
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
402
+     * @return \OC\Files\Cache\Scanner
403
+     */
404
+    public function getScanner($path = '', $storage = null) {
405
+        if (!$storage) {
406
+            $storage = $this;
407
+        }
408
+        return $this->getWrapperStorage()->getScanner($path, $storage);
409
+    }
410
+
411
+
412
+    /**
413
+     * get the user id of the owner of a file or folder
414
+     *
415
+     * @param string $path
416
+     * @return string
417
+     */
418
+    public function getOwner($path) {
419
+        return $this->getWrapperStorage()->getOwner($path);
420
+    }
421
+
422
+    /**
423
+     * get a watcher instance for the cache
424
+     *
425
+     * @param string $path
426
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
427
+     * @return \OC\Files\Cache\Watcher
428
+     */
429
+    public function getWatcher($path = '', $storage = null) {
430
+        if (!$storage) {
431
+            $storage = $this;
432
+        }
433
+        return $this->getWrapperStorage()->getWatcher($path, $storage);
434
+    }
435
+
436
+    public function getPropagator($storage = null) {
437
+        if (!$storage) {
438
+            $storage = $this;
439
+        }
440
+        return $this->getWrapperStorage()->getPropagator($storage);
441
+    }
442
+
443
+    public function getUpdater($storage = null) {
444
+        if (!$storage) {
445
+            $storage = $this;
446
+        }
447
+        return $this->getWrapperStorage()->getUpdater($storage);
448
+    }
449
+
450
+    /**
451
+     * @return \OC\Files\Cache\Storage
452
+     */
453
+    public function getStorageCache() {
454
+        return $this->getWrapperStorage()->getStorageCache();
455
+    }
456
+
457
+    /**
458
+     * get the ETag for a file or folder
459
+     *
460
+     * @param string $path
461
+     * @return string
462
+     */
463
+    public function getETag($path) {
464
+        return $this->getWrapperStorage()->getETag($path);
465
+    }
466
+
467
+    /**
468
+     * Returns true
469
+     *
470
+     * @return true
471
+     */
472
+    public function test() {
473
+        return $this->getWrapperStorage()->test();
474
+    }
475
+
476
+    /**
477
+     * Returns the wrapped storage's value for isLocal()
478
+     *
479
+     * @return bool wrapped storage's isLocal() value
480
+     */
481
+    public function isLocal() {
482
+        return $this->getWrapperStorage()->isLocal();
483
+    }
484
+
485
+    /**
486
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
487
+     *
488
+     * @param string $class
489
+     * @return bool
490
+     */
491
+    public function instanceOfStorage($class) {
492
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
493
+            // FIXME Temporary fix to keep existing checks working
494
+            $class = '\OCA\Files_Sharing\SharedStorage';
495
+        }
496
+        return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
497
+    }
498
+
499
+    /**
500
+     * Pass any methods custom to specific storage implementations to the wrapped storage
501
+     *
502
+     * @param string $method
503
+     * @param array $args
504
+     * @return mixed
505
+     */
506
+    public function __call($method, $args) {
507
+        return call_user_func_array([$this->getWrapperStorage(), $method], $args);
508
+    }
509
+
510
+    /**
511
+     * A custom storage implementation can return an url for direct download of a give file.
512
+     *
513
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
514
+     *
515
+     * @param string $path
516
+     * @return array
517
+     */
518
+    public function getDirectDownload($path) {
519
+        return $this->getWrapperStorage()->getDirectDownload($path);
520
+    }
521
+
522
+    /**
523
+     * Get availability of the storage
524
+     *
525
+     * @return array [ available, last_checked ]
526
+     */
527
+    public function getAvailability() {
528
+        return $this->getWrapperStorage()->getAvailability();
529
+    }
530
+
531
+    /**
532
+     * Set availability of the storage
533
+     *
534
+     * @param bool $isAvailable
535
+     */
536
+    public function setAvailability($isAvailable) {
537
+        $this->getWrapperStorage()->setAvailability($isAvailable);
538
+    }
539
+
540
+    /**
541
+     * @param string $path the path of the target folder
542
+     * @param string $fileName the name of the file itself
543
+     * @return void
544
+     * @throws InvalidPathException
545
+     */
546
+    public function verifyPath($path, $fileName) {
547
+        $this->getWrapperStorage()->verifyPath($path, $fileName);
548
+    }
549
+
550
+    /**
551
+     * @param IStorage $sourceStorage
552
+     * @param string $sourceInternalPath
553
+     * @param string $targetInternalPath
554
+     * @return bool
555
+     */
556
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
557
+        if ($sourceStorage === $this) {
558
+            return $this->copy($sourceInternalPath, $targetInternalPath);
559
+        }
560
+
561
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
562
+    }
563
+
564
+    /**
565
+     * @param IStorage $sourceStorage
566
+     * @param string $sourceInternalPath
567
+     * @param string $targetInternalPath
568
+     * @return bool
569
+     */
570
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
571
+        if ($sourceStorage === $this) {
572
+            return $this->rename($sourceInternalPath, $targetInternalPath);
573
+        }
574
+
575
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
576
+    }
577
+
578
+    /**
579
+     * @param string $path
580
+     * @return array
581
+     */
582
+    public function getMetaData($path) {
583
+        return $this->getWrapperStorage()->getMetaData($path);
584
+    }
585
+
586
+    /**
587
+     * @param string $path
588
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
589
+     * @param \OCP\Lock\ILockingProvider $provider
590
+     * @throws \OCP\Lock\LockedException
591
+     */
592
+    public function acquireLock($path, $type, ILockingProvider $provider) {
593
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
594
+            $this->getWrapperStorage()->acquireLock($path, $type, $provider);
595
+        }
596
+    }
597
+
598
+    /**
599
+     * @param string $path
600
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
601
+     * @param \OCP\Lock\ILockingProvider $provider
602
+     */
603
+    public function releaseLock($path, $type, ILockingProvider $provider) {
604
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
605
+            $this->getWrapperStorage()->releaseLock($path, $type, $provider);
606
+        }
607
+    }
608
+
609
+    /**
610
+     * @param string $path
611
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
612
+     * @param \OCP\Lock\ILockingProvider $provider
613
+     */
614
+    public function changeLock($path, $type, ILockingProvider $provider) {
615
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
616
+            $this->getWrapperStorage()->changeLock($path, $type, $provider);
617
+        }
618
+    }
619
+
620
+    /**
621
+     * @return bool
622
+     */
623
+    public function needsPartFile() {
624
+        return $this->getWrapperStorage()->needsPartFile();
625
+    }
626
+
627
+    public function writeStream(string $path, $stream, int $size = null): int {
628
+        $storage = $this->getWrapperStorage();
629
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
630
+            /** @var IWriteStreamStorage $storage */
631
+            return $storage->writeStream($path, $stream, $size);
632
+        } else {
633
+            $target = $this->fopen($path, 'w');
634
+            list($count, $result) = \OC_Helper::streamCopy($stream, $target);
635
+            fclose($stream);
636
+            fclose($target);
637
+            return $count;
638
+        }
639
+    }
640
+
641
+    public function getDirectoryContent($directory): \Traversable {
642
+        return $this->getWrapperStorage()->getDirectoryContent($directory);
643
+    }
644 644
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encryption.php 1 patch
Indentation   +988 added lines, -988 removed lines patch added patch discarded remove patch
@@ -51,992 +51,992 @@
 block discarded – undo
51 51
 use OCP\ILogger;
52 52
 
53 53
 class Encryption extends Wrapper {
54
-	use LocalTempFileTrait;
55
-
56
-	/** @var string */
57
-	private $mountPoint;
58
-
59
-	/** @var \OC\Encryption\Util */
60
-	private $util;
61
-
62
-	/** @var \OCP\Encryption\IManager */
63
-	private $encryptionManager;
64
-
65
-	/** @var \OCP\ILogger */
66
-	private $logger;
67
-
68
-	/** @var string */
69
-	private $uid;
70
-
71
-	/** @var array */
72
-	protected $unencryptedSize;
73
-
74
-	/** @var \OCP\Encryption\IFile */
75
-	private $fileHelper;
76
-
77
-	/** @var IMountPoint */
78
-	private $mount;
79
-
80
-	/** @var IStorage */
81
-	private $keyStorage;
82
-
83
-	/** @var Update */
84
-	private $update;
85
-
86
-	/** @var Manager */
87
-	private $mountManager;
88
-
89
-	/** @var array remember for which path we execute the repair step to avoid recursions */
90
-	private $fixUnencryptedSizeOf = [];
91
-
92
-	/** @var  ArrayCache */
93
-	private $arrayCache;
94
-
95
-	/**
96
-	 * @param array $parameters
97
-	 * @param IManager $encryptionManager
98
-	 * @param Util $util
99
-	 * @param ILogger $logger
100
-	 * @param IFile $fileHelper
101
-	 * @param string $uid
102
-	 * @param IStorage $keyStorage
103
-	 * @param Update $update
104
-	 * @param Manager $mountManager
105
-	 * @param ArrayCache $arrayCache
106
-	 */
107
-	public function __construct(
108
-		$parameters,
109
-		IManager $encryptionManager = null,
110
-		Util $util = null,
111
-		ILogger $logger = null,
112
-		IFile $fileHelper = null,
113
-		$uid = null,
114
-		IStorage $keyStorage = null,
115
-		Update $update = null,
116
-		Manager $mountManager = null,
117
-		ArrayCache $arrayCache = null
118
-	) {
119
-		$this->mountPoint = $parameters['mountPoint'];
120
-		$this->mount = $parameters['mount'];
121
-		$this->encryptionManager = $encryptionManager;
122
-		$this->util = $util;
123
-		$this->logger = $logger;
124
-		$this->uid = $uid;
125
-		$this->fileHelper = $fileHelper;
126
-		$this->keyStorage = $keyStorage;
127
-		$this->unencryptedSize = [];
128
-		$this->update = $update;
129
-		$this->mountManager = $mountManager;
130
-		$this->arrayCache = $arrayCache;
131
-		parent::__construct($parameters);
132
-	}
133
-
134
-	/**
135
-	 * see https://www.php.net/manual/en/function.filesize.php
136
-	 * The result for filesize when called on a folder is required to be 0
137
-	 *
138
-	 * @param string $path
139
-	 * @return int
140
-	 */
141
-	public function filesize($path) {
142
-		$fullPath = $this->getFullPath($path);
143
-
144
-		/** @var CacheEntry $info */
145
-		$info = $this->getCache()->get($path);
146
-		if (isset($this->unencryptedSize[$fullPath])) {
147
-			$size = $this->unencryptedSize[$fullPath];
148
-			// update file cache
149
-			if ($info instanceof ICacheEntry) {
150
-				$info = $info->getData();
151
-				$info['encrypted'] = $info['encryptedVersion'];
152
-			} else {
153
-				if (!is_array($info)) {
154
-					$info = [];
155
-				}
156
-				$info['encrypted'] = true;
157
-			}
158
-
159
-			$info['size'] = $size;
160
-			$this->getCache()->put($path, $info);
161
-
162
-			return $size;
163
-		}
164
-
165
-		if (isset($info['fileid']) && $info['encrypted']) {
166
-			return $this->verifyUnencryptedSize($path, $info['size']);
167
-		}
168
-
169
-		return $this->storage->filesize($path);
170
-	}
171
-
172
-	private function modifyMetaData(string $path, array $data): array {
173
-		$fullPath = $this->getFullPath($path);
174
-		$info = $this->getCache()->get($path);
175
-
176
-		if (isset($this->unencryptedSize[$fullPath])) {
177
-			$data['encrypted'] = true;
178
-			$data['size'] = $this->unencryptedSize[$fullPath];
179
-		} else {
180
-			if (isset($info['fileid']) && $info['encrypted']) {
181
-				$data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
182
-				$data['encrypted'] = true;
183
-			}
184
-		}
185
-
186
-		if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
187
-			$data['encryptedVersion'] = $info['encryptedVersion'];
188
-		}
189
-
190
-		return $data;
191
-	}
192
-
193
-	/**
194
-	 * @param string $path
195
-	 * @return array
196
-	 */
197
-	public function getMetaData($path) {
198
-		$data = $this->storage->getMetaData($path);
199
-		if (is_null($data)) {
200
-			return null;
201
-		}
202
-		return $this->modifyMetaData($path, $data);
203
-	}
204
-
205
-	public function getDirectoryContent($directory): \Traversable {
206
-		$parent = rtrim($directory, '/');
207
-		foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
208
-			yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
209
-		}
210
-	}
211
-
212
-	/**
213
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
214
-	 *
215
-	 * @param string $path
216
-	 * @return string
217
-	 */
218
-	public function file_get_contents($path) {
219
-		$encryptionModule = $this->getEncryptionModule($path);
220
-
221
-		if ($encryptionModule) {
222
-			$handle = $this->fopen($path, "r");
223
-			if (!$handle) {
224
-				return false;
225
-			}
226
-			$data = stream_get_contents($handle);
227
-			fclose($handle);
228
-			return $data;
229
-		}
230
-		return $this->storage->file_get_contents($path);
231
-	}
232
-
233
-	/**
234
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
235
-	 *
236
-	 * @param string $path
237
-	 * @param mixed $data
238
-	 * @return int|false
239
-	 */
240
-	public function file_put_contents($path, $data) {
241
-		// file put content will always be translated to a stream write
242
-		$handle = $this->fopen($path, 'w');
243
-		if (is_resource($handle)) {
244
-			$written = fwrite($handle, $data);
245
-			fclose($handle);
246
-			return $written;
247
-		}
248
-
249
-		return false;
250
-	}
251
-
252
-	/**
253
-	 * see https://www.php.net/manual/en/function.unlink.php
254
-	 *
255
-	 * @param string $path
256
-	 * @return bool
257
-	 */
258
-	public function unlink($path) {
259
-		$fullPath = $this->getFullPath($path);
260
-		if ($this->util->isExcluded($fullPath)) {
261
-			return $this->storage->unlink($path);
262
-		}
263
-
264
-		$encryptionModule = $this->getEncryptionModule($path);
265
-		if ($encryptionModule) {
266
-			$this->keyStorage->deleteAllFileKeys($fullPath);
267
-		}
268
-
269
-		return $this->storage->unlink($path);
270
-	}
271
-
272
-	/**
273
-	 * see https://www.php.net/manual/en/function.rename.php
274
-	 *
275
-	 * @param string $path1
276
-	 * @param string $path2
277
-	 * @return bool
278
-	 */
279
-	public function rename($path1, $path2) {
280
-		$result = $this->storage->rename($path1, $path2);
281
-
282
-		if ($result &&
283
-			// versions always use the keys from the original file, so we can skip
284
-			// this step for versions
285
-			$this->isVersion($path2) === false &&
286
-			$this->encryptionManager->isEnabled()) {
287
-			$source = $this->getFullPath($path1);
288
-			if (!$this->util->isExcluded($source)) {
289
-				$target = $this->getFullPath($path2);
290
-				if (isset($this->unencryptedSize[$source])) {
291
-					$this->unencryptedSize[$target] = $this->unencryptedSize[$source];
292
-				}
293
-				$this->keyStorage->renameKeys($source, $target);
294
-				$module = $this->getEncryptionModule($path2);
295
-				if ($module) {
296
-					$module->update($target, $this->uid, []);
297
-				}
298
-			}
299
-		}
300
-
301
-		return $result;
302
-	}
303
-
304
-	/**
305
-	 * see https://www.php.net/manual/en/function.rmdir.php
306
-	 *
307
-	 * @param string $path
308
-	 * @return bool
309
-	 */
310
-	public function rmdir($path) {
311
-		$result = $this->storage->rmdir($path);
312
-		$fullPath = $this->getFullPath($path);
313
-		if ($result &&
314
-			$this->util->isExcluded($fullPath) === false &&
315
-			$this->encryptionManager->isEnabled()
316
-		) {
317
-			$this->keyStorage->deleteAllFileKeys($fullPath);
318
-		}
319
-
320
-		return $result;
321
-	}
322
-
323
-	/**
324
-	 * check if a file can be read
325
-	 *
326
-	 * @param string $path
327
-	 * @return bool
328
-	 */
329
-	public function isReadable($path) {
330
-		$isReadable = true;
331
-
332
-		$metaData = $this->getMetaData($path);
333
-		if (
334
-			!$this->is_dir($path) &&
335
-			isset($metaData['encrypted']) &&
336
-			$metaData['encrypted'] === true
337
-		) {
338
-			$fullPath = $this->getFullPath($path);
339
-			$module = $this->getEncryptionModule($path);
340
-			$isReadable = $module->isReadable($fullPath, $this->uid);
341
-		}
342
-
343
-		return $this->storage->isReadable($path) && $isReadable;
344
-	}
345
-
346
-	/**
347
-	 * see https://www.php.net/manual/en/function.copy.php
348
-	 *
349
-	 * @param string $path1
350
-	 * @param string $path2
351
-	 * @return bool
352
-	 */
353
-	public function copy($path1, $path2) {
354
-		$source = $this->getFullPath($path1);
355
-
356
-		if ($this->util->isExcluded($source)) {
357
-			return $this->storage->copy($path1, $path2);
358
-		}
359
-
360
-		// need to stream copy file by file in case we copy between a encrypted
361
-		// and a unencrypted storage
362
-		$this->unlink($path2);
363
-		return $this->copyFromStorage($this, $path1, $path2);
364
-	}
365
-
366
-	/**
367
-	 * see https://www.php.net/manual/en/function.fopen.php
368
-	 *
369
-	 * @param string $path
370
-	 * @param string $mode
371
-	 * @return resource|bool
372
-	 * @throws GenericEncryptionException
373
-	 * @throws ModuleDoesNotExistsException
374
-	 */
375
-	public function fopen($path, $mode) {
376
-
377
-		// check if the file is stored in the array cache, this means that we
378
-		// copy a file over to the versions folder, in this case we don't want to
379
-		// decrypt it
380
-		if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
381
-			$this->arrayCache->remove('encryption_copy_version_' . $path);
382
-			return $this->storage->fopen($path, $mode);
383
-		}
384
-
385
-		$encryptionEnabled = $this->encryptionManager->isEnabled();
386
-		$shouldEncrypt = false;
387
-		$encryptionModule = null;
388
-		$header = $this->getHeader($path);
389
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
390
-		$fullPath = $this->getFullPath($path);
391
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
392
-
393
-		if ($this->util->isExcluded($fullPath) === false) {
394
-			$size = $unencryptedSize = 0;
395
-			$realFile = $this->util->stripPartialFileExtension($path);
396
-			$targetExists = $this->file_exists($realFile) || $this->file_exists($path);
397
-			$targetIsEncrypted = false;
398
-			if ($targetExists) {
399
-				// in case the file exists we require the explicit module as
400
-				// specified in the file header - otherwise we need to fail hard to
401
-				// prevent data loss on client side
402
-				if (!empty($encryptionModuleId)) {
403
-					$targetIsEncrypted = true;
404
-					$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
405
-				}
406
-
407
-				if ($this->file_exists($path)) {
408
-					$size = $this->storage->filesize($path);
409
-					$unencryptedSize = $this->filesize($path);
410
-				} else {
411
-					$size = $unencryptedSize = 0;
412
-				}
413
-			}
414
-
415
-			try {
416
-				if (
417
-					$mode === 'w'
418
-					|| $mode === 'w+'
419
-					|| $mode === 'wb'
420
-					|| $mode === 'wb+'
421
-				) {
422
-					// if we update a encrypted file with a un-encrypted one we change the db flag
423
-					if ($targetIsEncrypted && $encryptionEnabled === false) {
424
-						$cache = $this->storage->getCache();
425
-						if ($cache) {
426
-							$entry = $cache->get($path);
427
-							$cache->update($entry->getId(), ['encrypted' => 0]);
428
-						}
429
-					}
430
-					if ($encryptionEnabled) {
431
-						// if $encryptionModuleId is empty, the default module will be used
432
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
433
-						$shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
434
-						$signed = true;
435
-					}
436
-				} else {
437
-					$info = $this->getCache()->get($path);
438
-					// only get encryption module if we found one in the header
439
-					// or if file should be encrypted according to the file cache
440
-					if (!empty($encryptionModuleId)) {
441
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
442
-						$shouldEncrypt = true;
443
-					} elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
444
-						// we come from a old installation. No header and/or no module defined
445
-						// but the file is encrypted. In this case we need to use the
446
-						// OC_DEFAULT_MODULE to read the file
447
-						$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
448
-						$shouldEncrypt = true;
449
-						$targetIsEncrypted = true;
450
-					}
451
-				}
452
-			} catch (ModuleDoesNotExistsException $e) {
453
-				$this->logger->logException($e, [
454
-					'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
455
-					'level' => ILogger::WARN,
456
-					'app' => 'core',
457
-				]);
458
-			}
459
-
460
-			// encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
461
-			if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
462
-				if (!$targetExists || !$targetIsEncrypted) {
463
-					$shouldEncrypt = false;
464
-				}
465
-			}
466
-
467
-			if ($shouldEncrypt === true && $encryptionModule !== null) {
468
-				$headerSize = $this->getHeaderSize($path);
469
-				$source = $this->storage->fopen($path, $mode);
470
-				if (!is_resource($source)) {
471
-					return false;
472
-				}
473
-				$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
474
-					$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
475
-					$size, $unencryptedSize, $headerSize, $signed);
476
-				return $handle;
477
-			}
478
-		}
479
-
480
-		return $this->storage->fopen($path, $mode);
481
-	}
482
-
483
-
484
-	/**
485
-	 * perform some plausibility checks if the the unencrypted size is correct.
486
-	 * If not, we calculate the correct unencrypted size and return it
487
-	 *
488
-	 * @param string $path internal path relative to the storage root
489
-	 * @param int $unencryptedSize size of the unencrypted file
490
-	 *
491
-	 * @return int unencrypted size
492
-	 */
493
-	protected function verifyUnencryptedSize($path, $unencryptedSize) {
494
-		$size = $this->storage->filesize($path);
495
-		$result = $unencryptedSize;
496
-
497
-		if ($unencryptedSize < 0 ||
498
-			($size > 0 && $unencryptedSize === $size)
499
-		) {
500
-			// check if we already calculate the unencrypted size for the
501
-			// given path to avoid recursions
502
-			if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
503
-				$this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
504
-				try {
505
-					$result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
506
-				} catch (\Exception $e) {
507
-					$this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path);
508
-					$this->logger->logException($e);
509
-				}
510
-				unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
511
-			}
512
-		}
513
-
514
-		return $result;
515
-	}
516
-
517
-	/**
518
-	 * calculate the unencrypted size
519
-	 *
520
-	 * @param string $path internal path relative to the storage root
521
-	 * @param int $size size of the physical file
522
-	 * @param int $unencryptedSize size of the unencrypted file
523
-	 *
524
-	 * @return int calculated unencrypted size
525
-	 */
526
-	protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
527
-		$headerSize = $this->getHeaderSize($path);
528
-		$header = $this->getHeader($path);
529
-		$encryptionModule = $this->getEncryptionModule($path);
530
-
531
-		$stream = $this->storage->fopen($path, 'r');
532
-
533
-		// if we couldn't open the file we return the old unencrypted size
534
-		if (!is_resource($stream)) {
535
-			$this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
536
-			return $unencryptedSize;
537
-		}
538
-
539
-		$newUnencryptedSize = 0;
540
-		$size -= $headerSize;
541
-		$blockSize = $this->util->getBlockSize();
542
-
543
-		// if a header exists we skip it
544
-		if ($headerSize > 0) {
545
-			fread($stream, $headerSize);
546
-		}
547
-
548
-		// fast path, else the calculation for $lastChunkNr is bogus
549
-		if ($size === 0) {
550
-			return 0;
551
-		}
552
-
553
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
554
-		$unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
555
-
556
-		// calculate last chunk nr
557
-		// next highest is end of chunks, one subtracted is last one
558
-		// we have to read the last chunk, we can't just calculate it (because of padding etc)
559
-
560
-		$lastChunkNr = ceil($size / $blockSize) - 1;
561
-		// calculate last chunk position
562
-		$lastChunkPos = ($lastChunkNr * $blockSize);
563
-		// try to fseek to the last chunk, if it fails we have to read the whole file
564
-		if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
565
-			$newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
566
-		}
567
-
568
-		$lastChunkContentEncrypted = '';
569
-		$count = $blockSize;
570
-
571
-		while ($count > 0) {
572
-			$data = fread($stream, $blockSize);
573
-			$count = strlen($data);
574
-			$lastChunkContentEncrypted .= $data;
575
-			if (strlen($lastChunkContentEncrypted) > $blockSize) {
576
-				$newUnencryptedSize += $unencryptedBlockSize;
577
-				$lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize);
578
-			}
579
-		}
580
-
581
-		fclose($stream);
582
-
583
-		// we have to decrypt the last chunk to get it actual size
584
-		$encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
585
-		$decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
586
-		$decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
587
-
588
-		// calc the real file size with the size of the last chunk
589
-		$newUnencryptedSize += strlen($decryptedLastChunk);
590
-
591
-		$this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
592
-
593
-		// write to cache if applicable
594
-		$cache = $this->storage->getCache();
595
-		if ($cache) {
596
-			$entry = $cache->get($path);
597
-			$cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
598
-		}
599
-
600
-		return $newUnencryptedSize;
601
-	}
602
-
603
-	/**
604
-	 * @param Storage\IStorage $sourceStorage
605
-	 * @param string $sourceInternalPath
606
-	 * @param string $targetInternalPath
607
-	 * @param bool $preserveMtime
608
-	 * @return bool
609
-	 */
610
-	public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
611
-		if ($sourceStorage === $this) {
612
-			return $this->rename($sourceInternalPath, $targetInternalPath);
613
-		}
614
-
615
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
616
-		// - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
617
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
618
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
619
-		// - remove $this->copyBetweenStorage
620
-
621
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
622
-			return false;
623
-		}
624
-
625
-		$result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
626
-		if ($result) {
627
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
628
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
629
-			} else {
630
-				$result &= $sourceStorage->unlink($sourceInternalPath);
631
-			}
632
-		}
633
-		return $result;
634
-	}
635
-
636
-
637
-	/**
638
-	 * @param Storage\IStorage $sourceStorage
639
-	 * @param string $sourceInternalPath
640
-	 * @param string $targetInternalPath
641
-	 * @param bool $preserveMtime
642
-	 * @param bool $isRename
643
-	 * @return bool
644
-	 */
645
-	public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
646
-
647
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
648
-		// - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
649
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
650
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
651
-		// - remove $this->copyBetweenStorage
652
-
653
-		return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
654
-	}
655
-
656
-	/**
657
-	 * Update the encrypted cache version in the database
658
-	 *
659
-	 * @param Storage\IStorage $sourceStorage
660
-	 * @param string $sourceInternalPath
661
-	 * @param string $targetInternalPath
662
-	 * @param bool $isRename
663
-	 * @param bool $keepEncryptionVersion
664
-	 */
665
-	private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
666
-		$isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
667
-		$cacheInformation = [
668
-			'encrypted' => $isEncrypted,
669
-		];
670
-		if ($isEncrypted) {
671
-			$encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
672
-
673
-			// In case of a move operation from an unencrypted to an encrypted
674
-			// storage the old encrypted version would stay with "0" while the
675
-			// correct value would be "1". Thus we manually set the value to "1"
676
-			// for those cases.
677
-			// See also https://github.com/owncloud/core/issues/23078
678
-			if ($encryptedVersion === 0 || !$keepEncryptionVersion) {
679
-				$encryptedVersion = 1;
680
-			}
681
-
682
-			$cacheInformation['encryptedVersion'] = $encryptedVersion;
683
-		}
684
-
685
-		// in case of a rename we need to manipulate the source cache because
686
-		// this information will be kept for the new target
687
-		if ($isRename) {
688
-			$sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
689
-		} else {
690
-			$this->getCache()->put($targetInternalPath, $cacheInformation);
691
-		}
692
-	}
693
-
694
-	/**
695
-	 * copy file between two storages
696
-	 *
697
-	 * @param Storage\IStorage $sourceStorage
698
-	 * @param string $sourceInternalPath
699
-	 * @param string $targetInternalPath
700
-	 * @param bool $preserveMtime
701
-	 * @param bool $isRename
702
-	 * @return bool
703
-	 * @throws \Exception
704
-	 */
705
-	private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
706
-
707
-		// for versions we have nothing to do, because versions should always use the
708
-		// key from the original file. Just create a 1:1 copy and done
709
-		if ($this->isVersion($targetInternalPath) ||
710
-			$this->isVersion($sourceInternalPath)) {
711
-			// remember that we try to create a version so that we can detect it during
712
-			// fopen($sourceInternalPath) and by-pass the encryption in order to
713
-			// create a 1:1 copy of the file
714
-			$this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
715
-			$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
716
-			$this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
717
-			if ($result) {
718
-				$info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
719
-				// make sure that we update the unencrypted size for the version
720
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
721
-					$this->updateUnencryptedSize(
722
-						$this->getFullPath($targetInternalPath),
723
-						$info['size']
724
-					);
725
-				}
726
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
727
-			}
728
-			return $result;
729
-		}
730
-
731
-		// first copy the keys that we reuse the existing file key on the target location
732
-		// and don't create a new one which would break versions for example.
733
-		$mount = $this->mountManager->findByStorageId($sourceStorage->getId());
734
-		if (count($mount) === 1) {
735
-			$mountPoint = $mount[0]->getMountPoint();
736
-			$source = $mountPoint . '/' . $sourceInternalPath;
737
-			$target = $this->getFullPath($targetInternalPath);
738
-			$this->copyKeys($source, $target);
739
-		} else {
740
-			$this->logger->error('Could not find mount point, can\'t keep encryption keys');
741
-		}
742
-
743
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
744
-			$dh = $sourceStorage->opendir($sourceInternalPath);
745
-			$result = $this->mkdir($targetInternalPath);
746
-			if (is_resource($dh)) {
747
-				while ($result and ($file = readdir($dh)) !== false) {
748
-					if (!Filesystem::isIgnoredDir($file)) {
749
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
750
-					}
751
-				}
752
-			}
753
-		} else {
754
-			try {
755
-				$source = $sourceStorage->fopen($sourceInternalPath, 'r');
756
-				$target = $this->fopen($targetInternalPath, 'w');
757
-				[, $result] = \OC_Helper::streamCopy($source, $target);
758
-				fclose($source);
759
-				fclose($target);
760
-			} catch (\Exception $e) {
761
-				fclose($source);
762
-				fclose($target);
763
-				throw $e;
764
-			}
765
-			if ($result) {
766
-				if ($preserveMtime) {
767
-					$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
768
-				}
769
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
770
-			} else {
771
-				// delete partially written target file
772
-				$this->unlink($targetInternalPath);
773
-				// delete cache entry that was created by fopen
774
-				$this->getCache()->remove($targetInternalPath);
775
-			}
776
-		}
777
-		return (bool)$result;
778
-	}
779
-
780
-	/**
781
-	 * get the path to a local version of the file.
782
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
783
-	 *
784
-	 * @param string $path
785
-	 * @return string
786
-	 */
787
-	public function getLocalFile($path) {
788
-		if ($this->encryptionManager->isEnabled()) {
789
-			$cachedFile = $this->getCachedFile($path);
790
-			if (is_string($cachedFile)) {
791
-				return $cachedFile;
792
-			}
793
-		}
794
-		return $this->storage->getLocalFile($path);
795
-	}
796
-
797
-	/**
798
-	 * Returns the wrapped storage's value for isLocal()
799
-	 *
800
-	 * @return bool wrapped storage's isLocal() value
801
-	 */
802
-	public function isLocal() {
803
-		if ($this->encryptionManager->isEnabled()) {
804
-			return false;
805
-		}
806
-		return $this->storage->isLocal();
807
-	}
808
-
809
-	/**
810
-	 * see https://www.php.net/manual/en/function.stat.php
811
-	 * only the following keys are required in the result: size and mtime
812
-	 *
813
-	 * @param string $path
814
-	 * @return array
815
-	 */
816
-	public function stat($path) {
817
-		$stat = $this->storage->stat($path);
818
-		$fileSize = $this->filesize($path);
819
-		$stat['size'] = $fileSize;
820
-		$stat[7] = $fileSize;
821
-		$stat['hasHeader'] = $this->getHeaderSize($path) > 0;
822
-		return $stat;
823
-	}
824
-
825
-	/**
826
-	 * see https://www.php.net/manual/en/function.hash.php
827
-	 *
828
-	 * @param string $type
829
-	 * @param string $path
830
-	 * @param bool $raw
831
-	 * @return string
832
-	 */
833
-	public function hash($type, $path, $raw = false) {
834
-		$fh = $this->fopen($path, 'rb');
835
-		$ctx = hash_init($type);
836
-		hash_update_stream($ctx, $fh);
837
-		fclose($fh);
838
-		return hash_final($ctx, $raw);
839
-	}
840
-
841
-	/**
842
-	 * return full path, including mount point
843
-	 *
844
-	 * @param string $path relative to mount point
845
-	 * @return string full path including mount point
846
-	 */
847
-	protected function getFullPath($path) {
848
-		return Filesystem::normalizePath($this->mountPoint . '/' . $path);
849
-	}
850
-
851
-	/**
852
-	 * read first block of encrypted file, typically this will contain the
853
-	 * encryption header
854
-	 *
855
-	 * @param string $path
856
-	 * @return string
857
-	 */
858
-	protected function readFirstBlock($path) {
859
-		$firstBlock = '';
860
-		if ($this->storage->file_exists($path)) {
861
-			$handle = $this->storage->fopen($path, 'r');
862
-			$firstBlock = fread($handle, $this->util->getHeaderSize());
863
-			fclose($handle);
864
-		}
865
-		return $firstBlock;
866
-	}
867
-
868
-	/**
869
-	 * return header size of given file
870
-	 *
871
-	 * @param string $path
872
-	 * @return int
873
-	 */
874
-	protected function getHeaderSize($path) {
875
-		$headerSize = 0;
876
-		$realFile = $this->util->stripPartialFileExtension($path);
877
-		if ($this->storage->file_exists($realFile)) {
878
-			$path = $realFile;
879
-		}
880
-		$firstBlock = $this->readFirstBlock($path);
881
-
882
-		if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
883
-			$headerSize = $this->util->getHeaderSize();
884
-		}
885
-
886
-		return $headerSize;
887
-	}
888
-
889
-	/**
890
-	 * parse raw header to array
891
-	 *
892
-	 * @param string $rawHeader
893
-	 * @return array
894
-	 */
895
-	protected function parseRawHeader($rawHeader) {
896
-		$result = [];
897
-		if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
898
-			$header = $rawHeader;
899
-			$endAt = strpos($header, Util::HEADER_END);
900
-			if ($endAt !== false) {
901
-				$header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
902
-
903
-				// +1 to not start with an ':' which would result in empty element at the beginning
904
-				$exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
905
-
906
-				$element = array_shift($exploded);
907
-				while ($element !== Util::HEADER_END) {
908
-					$result[$element] = array_shift($exploded);
909
-					$element = array_shift($exploded);
910
-				}
911
-			}
912
-		}
913
-
914
-		return $result;
915
-	}
916
-
917
-	/**
918
-	 * read header from file
919
-	 *
920
-	 * @param string $path
921
-	 * @return array
922
-	 */
923
-	protected function getHeader($path) {
924
-		$realFile = $this->util->stripPartialFileExtension($path);
925
-		$exists = $this->storage->file_exists($realFile);
926
-		if ($exists) {
927
-			$path = $realFile;
928
-		}
929
-
930
-		$firstBlock = $this->readFirstBlock($path);
931
-		$result = $this->parseRawHeader($firstBlock);
932
-
933
-		// if the header doesn't contain a encryption module we check if it is a
934
-		// legacy file. If true, we add the default encryption module
935
-		if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
936
-			if (!empty($result)) {
937
-				$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
938
-			} elseif ($exists) {
939
-				// if the header was empty we have to check first if it is a encrypted file at all
940
-				// We would do query to filecache only if we know that entry in filecache exists
941
-				$info = $this->getCache()->get($path);
942
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
943
-					$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
944
-				}
945
-			}
946
-		}
947
-
948
-		return $result;
949
-	}
950
-
951
-	/**
952
-	 * read encryption module needed to read/write the file located at $path
953
-	 *
954
-	 * @param string $path
955
-	 * @return null|\OCP\Encryption\IEncryptionModule
956
-	 * @throws ModuleDoesNotExistsException
957
-	 * @throws \Exception
958
-	 */
959
-	protected function getEncryptionModule($path) {
960
-		$encryptionModule = null;
961
-		$header = $this->getHeader($path);
962
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
963
-		if (!empty($encryptionModuleId)) {
964
-			try {
965
-				$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
966
-			} catch (ModuleDoesNotExistsException $e) {
967
-				$this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
968
-				throw $e;
969
-			}
970
-		}
971
-
972
-		return $encryptionModule;
973
-	}
974
-
975
-	/**
976
-	 * @param string $path
977
-	 * @param int $unencryptedSize
978
-	 */
979
-	public function updateUnencryptedSize($path, $unencryptedSize) {
980
-		$this->unencryptedSize[$path] = $unencryptedSize;
981
-	}
982
-
983
-	/**
984
-	 * copy keys to new location
985
-	 *
986
-	 * @param string $source path relative to data/
987
-	 * @param string $target path relative to data/
988
-	 * @return bool
989
-	 */
990
-	protected function copyKeys($source, $target) {
991
-		if (!$this->util->isExcluded($source)) {
992
-			return $this->keyStorage->copyKeys($source, $target);
993
-		}
994
-
995
-		return false;
996
-	}
997
-
998
-	/**
999
-	 * check if path points to a files version
1000
-	 *
1001
-	 * @param $path
1002
-	 * @return bool
1003
-	 */
1004
-	protected function isVersion($path) {
1005
-		$normalized = Filesystem::normalizePath($path);
1006
-		return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1007
-	}
1008
-
1009
-	/**
1010
-	 * check if the given storage should be encrypted or not
1011
-	 *
1012
-	 * @param $path
1013
-	 * @return bool
1014
-	 */
1015
-	protected function shouldEncrypt($path) {
1016
-		$fullPath = $this->getFullPath($path);
1017
-		$mountPointConfig = $this->mount->getOption('encrypt', true);
1018
-		if ($mountPointConfig === false) {
1019
-			return false;
1020
-		}
1021
-
1022
-		try {
1023
-			$encryptionModule = $this->getEncryptionModule($fullPath);
1024
-		} catch (ModuleDoesNotExistsException $e) {
1025
-			return false;
1026
-		}
1027
-
1028
-		if ($encryptionModule === null) {
1029
-			$encryptionModule = $this->encryptionManager->getEncryptionModule();
1030
-		}
1031
-
1032
-		return $encryptionModule->shouldEncrypt($fullPath);
1033
-	}
1034
-
1035
-	public function writeStream(string $path, $stream, int $size = null): int {
1036
-		// always fall back to fopen
1037
-		$target = $this->fopen($path, 'w');
1038
-		[$count, $result] = \OC_Helper::streamCopy($stream, $target);
1039
-		fclose($target);
1040
-		return $count;
1041
-	}
54
+    use LocalTempFileTrait;
55
+
56
+    /** @var string */
57
+    private $mountPoint;
58
+
59
+    /** @var \OC\Encryption\Util */
60
+    private $util;
61
+
62
+    /** @var \OCP\Encryption\IManager */
63
+    private $encryptionManager;
64
+
65
+    /** @var \OCP\ILogger */
66
+    private $logger;
67
+
68
+    /** @var string */
69
+    private $uid;
70
+
71
+    /** @var array */
72
+    protected $unencryptedSize;
73
+
74
+    /** @var \OCP\Encryption\IFile */
75
+    private $fileHelper;
76
+
77
+    /** @var IMountPoint */
78
+    private $mount;
79
+
80
+    /** @var IStorage */
81
+    private $keyStorage;
82
+
83
+    /** @var Update */
84
+    private $update;
85
+
86
+    /** @var Manager */
87
+    private $mountManager;
88
+
89
+    /** @var array remember for which path we execute the repair step to avoid recursions */
90
+    private $fixUnencryptedSizeOf = [];
91
+
92
+    /** @var  ArrayCache */
93
+    private $arrayCache;
94
+
95
+    /**
96
+     * @param array $parameters
97
+     * @param IManager $encryptionManager
98
+     * @param Util $util
99
+     * @param ILogger $logger
100
+     * @param IFile $fileHelper
101
+     * @param string $uid
102
+     * @param IStorage $keyStorage
103
+     * @param Update $update
104
+     * @param Manager $mountManager
105
+     * @param ArrayCache $arrayCache
106
+     */
107
+    public function __construct(
108
+        $parameters,
109
+        IManager $encryptionManager = null,
110
+        Util $util = null,
111
+        ILogger $logger = null,
112
+        IFile $fileHelper = null,
113
+        $uid = null,
114
+        IStorage $keyStorage = null,
115
+        Update $update = null,
116
+        Manager $mountManager = null,
117
+        ArrayCache $arrayCache = null
118
+    ) {
119
+        $this->mountPoint = $parameters['mountPoint'];
120
+        $this->mount = $parameters['mount'];
121
+        $this->encryptionManager = $encryptionManager;
122
+        $this->util = $util;
123
+        $this->logger = $logger;
124
+        $this->uid = $uid;
125
+        $this->fileHelper = $fileHelper;
126
+        $this->keyStorage = $keyStorage;
127
+        $this->unencryptedSize = [];
128
+        $this->update = $update;
129
+        $this->mountManager = $mountManager;
130
+        $this->arrayCache = $arrayCache;
131
+        parent::__construct($parameters);
132
+    }
133
+
134
+    /**
135
+     * see https://www.php.net/manual/en/function.filesize.php
136
+     * The result for filesize when called on a folder is required to be 0
137
+     *
138
+     * @param string $path
139
+     * @return int
140
+     */
141
+    public function filesize($path) {
142
+        $fullPath = $this->getFullPath($path);
143
+
144
+        /** @var CacheEntry $info */
145
+        $info = $this->getCache()->get($path);
146
+        if (isset($this->unencryptedSize[$fullPath])) {
147
+            $size = $this->unencryptedSize[$fullPath];
148
+            // update file cache
149
+            if ($info instanceof ICacheEntry) {
150
+                $info = $info->getData();
151
+                $info['encrypted'] = $info['encryptedVersion'];
152
+            } else {
153
+                if (!is_array($info)) {
154
+                    $info = [];
155
+                }
156
+                $info['encrypted'] = true;
157
+            }
158
+
159
+            $info['size'] = $size;
160
+            $this->getCache()->put($path, $info);
161
+
162
+            return $size;
163
+        }
164
+
165
+        if (isset($info['fileid']) && $info['encrypted']) {
166
+            return $this->verifyUnencryptedSize($path, $info['size']);
167
+        }
168
+
169
+        return $this->storage->filesize($path);
170
+    }
171
+
172
+    private function modifyMetaData(string $path, array $data): array {
173
+        $fullPath = $this->getFullPath($path);
174
+        $info = $this->getCache()->get($path);
175
+
176
+        if (isset($this->unencryptedSize[$fullPath])) {
177
+            $data['encrypted'] = true;
178
+            $data['size'] = $this->unencryptedSize[$fullPath];
179
+        } else {
180
+            if (isset($info['fileid']) && $info['encrypted']) {
181
+                $data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
182
+                $data['encrypted'] = true;
183
+            }
184
+        }
185
+
186
+        if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
187
+            $data['encryptedVersion'] = $info['encryptedVersion'];
188
+        }
189
+
190
+        return $data;
191
+    }
192
+
193
+    /**
194
+     * @param string $path
195
+     * @return array
196
+     */
197
+    public function getMetaData($path) {
198
+        $data = $this->storage->getMetaData($path);
199
+        if (is_null($data)) {
200
+            return null;
201
+        }
202
+        return $this->modifyMetaData($path, $data);
203
+    }
204
+
205
+    public function getDirectoryContent($directory): \Traversable {
206
+        $parent = rtrim($directory, '/');
207
+        foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
208
+            yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
209
+        }
210
+    }
211
+
212
+    /**
213
+     * see https://www.php.net/manual/en/function.file_get_contents.php
214
+     *
215
+     * @param string $path
216
+     * @return string
217
+     */
218
+    public function file_get_contents($path) {
219
+        $encryptionModule = $this->getEncryptionModule($path);
220
+
221
+        if ($encryptionModule) {
222
+            $handle = $this->fopen($path, "r");
223
+            if (!$handle) {
224
+                return false;
225
+            }
226
+            $data = stream_get_contents($handle);
227
+            fclose($handle);
228
+            return $data;
229
+        }
230
+        return $this->storage->file_get_contents($path);
231
+    }
232
+
233
+    /**
234
+     * see https://www.php.net/manual/en/function.file_put_contents.php
235
+     *
236
+     * @param string $path
237
+     * @param mixed $data
238
+     * @return int|false
239
+     */
240
+    public function file_put_contents($path, $data) {
241
+        // file put content will always be translated to a stream write
242
+        $handle = $this->fopen($path, 'w');
243
+        if (is_resource($handle)) {
244
+            $written = fwrite($handle, $data);
245
+            fclose($handle);
246
+            return $written;
247
+        }
248
+
249
+        return false;
250
+    }
251
+
252
+    /**
253
+     * see https://www.php.net/manual/en/function.unlink.php
254
+     *
255
+     * @param string $path
256
+     * @return bool
257
+     */
258
+    public function unlink($path) {
259
+        $fullPath = $this->getFullPath($path);
260
+        if ($this->util->isExcluded($fullPath)) {
261
+            return $this->storage->unlink($path);
262
+        }
263
+
264
+        $encryptionModule = $this->getEncryptionModule($path);
265
+        if ($encryptionModule) {
266
+            $this->keyStorage->deleteAllFileKeys($fullPath);
267
+        }
268
+
269
+        return $this->storage->unlink($path);
270
+    }
271
+
272
+    /**
273
+     * see https://www.php.net/manual/en/function.rename.php
274
+     *
275
+     * @param string $path1
276
+     * @param string $path2
277
+     * @return bool
278
+     */
279
+    public function rename($path1, $path2) {
280
+        $result = $this->storage->rename($path1, $path2);
281
+
282
+        if ($result &&
283
+            // versions always use the keys from the original file, so we can skip
284
+            // this step for versions
285
+            $this->isVersion($path2) === false &&
286
+            $this->encryptionManager->isEnabled()) {
287
+            $source = $this->getFullPath($path1);
288
+            if (!$this->util->isExcluded($source)) {
289
+                $target = $this->getFullPath($path2);
290
+                if (isset($this->unencryptedSize[$source])) {
291
+                    $this->unencryptedSize[$target] = $this->unencryptedSize[$source];
292
+                }
293
+                $this->keyStorage->renameKeys($source, $target);
294
+                $module = $this->getEncryptionModule($path2);
295
+                if ($module) {
296
+                    $module->update($target, $this->uid, []);
297
+                }
298
+            }
299
+        }
300
+
301
+        return $result;
302
+    }
303
+
304
+    /**
305
+     * see https://www.php.net/manual/en/function.rmdir.php
306
+     *
307
+     * @param string $path
308
+     * @return bool
309
+     */
310
+    public function rmdir($path) {
311
+        $result = $this->storage->rmdir($path);
312
+        $fullPath = $this->getFullPath($path);
313
+        if ($result &&
314
+            $this->util->isExcluded($fullPath) === false &&
315
+            $this->encryptionManager->isEnabled()
316
+        ) {
317
+            $this->keyStorage->deleteAllFileKeys($fullPath);
318
+        }
319
+
320
+        return $result;
321
+    }
322
+
323
+    /**
324
+     * check if a file can be read
325
+     *
326
+     * @param string $path
327
+     * @return bool
328
+     */
329
+    public function isReadable($path) {
330
+        $isReadable = true;
331
+
332
+        $metaData = $this->getMetaData($path);
333
+        if (
334
+            !$this->is_dir($path) &&
335
+            isset($metaData['encrypted']) &&
336
+            $metaData['encrypted'] === true
337
+        ) {
338
+            $fullPath = $this->getFullPath($path);
339
+            $module = $this->getEncryptionModule($path);
340
+            $isReadable = $module->isReadable($fullPath, $this->uid);
341
+        }
342
+
343
+        return $this->storage->isReadable($path) && $isReadable;
344
+    }
345
+
346
+    /**
347
+     * see https://www.php.net/manual/en/function.copy.php
348
+     *
349
+     * @param string $path1
350
+     * @param string $path2
351
+     * @return bool
352
+     */
353
+    public function copy($path1, $path2) {
354
+        $source = $this->getFullPath($path1);
355
+
356
+        if ($this->util->isExcluded($source)) {
357
+            return $this->storage->copy($path1, $path2);
358
+        }
359
+
360
+        // need to stream copy file by file in case we copy between a encrypted
361
+        // and a unencrypted storage
362
+        $this->unlink($path2);
363
+        return $this->copyFromStorage($this, $path1, $path2);
364
+    }
365
+
366
+    /**
367
+     * see https://www.php.net/manual/en/function.fopen.php
368
+     *
369
+     * @param string $path
370
+     * @param string $mode
371
+     * @return resource|bool
372
+     * @throws GenericEncryptionException
373
+     * @throws ModuleDoesNotExistsException
374
+     */
375
+    public function fopen($path, $mode) {
376
+
377
+        // check if the file is stored in the array cache, this means that we
378
+        // copy a file over to the versions folder, in this case we don't want to
379
+        // decrypt it
380
+        if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
381
+            $this->arrayCache->remove('encryption_copy_version_' . $path);
382
+            return $this->storage->fopen($path, $mode);
383
+        }
384
+
385
+        $encryptionEnabled = $this->encryptionManager->isEnabled();
386
+        $shouldEncrypt = false;
387
+        $encryptionModule = null;
388
+        $header = $this->getHeader($path);
389
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
390
+        $fullPath = $this->getFullPath($path);
391
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
392
+
393
+        if ($this->util->isExcluded($fullPath) === false) {
394
+            $size = $unencryptedSize = 0;
395
+            $realFile = $this->util->stripPartialFileExtension($path);
396
+            $targetExists = $this->file_exists($realFile) || $this->file_exists($path);
397
+            $targetIsEncrypted = false;
398
+            if ($targetExists) {
399
+                // in case the file exists we require the explicit module as
400
+                // specified in the file header - otherwise we need to fail hard to
401
+                // prevent data loss on client side
402
+                if (!empty($encryptionModuleId)) {
403
+                    $targetIsEncrypted = true;
404
+                    $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
405
+                }
406
+
407
+                if ($this->file_exists($path)) {
408
+                    $size = $this->storage->filesize($path);
409
+                    $unencryptedSize = $this->filesize($path);
410
+                } else {
411
+                    $size = $unencryptedSize = 0;
412
+                }
413
+            }
414
+
415
+            try {
416
+                if (
417
+                    $mode === 'w'
418
+                    || $mode === 'w+'
419
+                    || $mode === 'wb'
420
+                    || $mode === 'wb+'
421
+                ) {
422
+                    // if we update a encrypted file with a un-encrypted one we change the db flag
423
+                    if ($targetIsEncrypted && $encryptionEnabled === false) {
424
+                        $cache = $this->storage->getCache();
425
+                        if ($cache) {
426
+                            $entry = $cache->get($path);
427
+                            $cache->update($entry->getId(), ['encrypted' => 0]);
428
+                        }
429
+                    }
430
+                    if ($encryptionEnabled) {
431
+                        // if $encryptionModuleId is empty, the default module will be used
432
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
433
+                        $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
434
+                        $signed = true;
435
+                    }
436
+                } else {
437
+                    $info = $this->getCache()->get($path);
438
+                    // only get encryption module if we found one in the header
439
+                    // or if file should be encrypted according to the file cache
440
+                    if (!empty($encryptionModuleId)) {
441
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
442
+                        $shouldEncrypt = true;
443
+                    } elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
444
+                        // we come from a old installation. No header and/or no module defined
445
+                        // but the file is encrypted. In this case we need to use the
446
+                        // OC_DEFAULT_MODULE to read the file
447
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
448
+                        $shouldEncrypt = true;
449
+                        $targetIsEncrypted = true;
450
+                    }
451
+                }
452
+            } catch (ModuleDoesNotExistsException $e) {
453
+                $this->logger->logException($e, [
454
+                    'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
455
+                    'level' => ILogger::WARN,
456
+                    'app' => 'core',
457
+                ]);
458
+            }
459
+
460
+            // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
461
+            if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
462
+                if (!$targetExists || !$targetIsEncrypted) {
463
+                    $shouldEncrypt = false;
464
+                }
465
+            }
466
+
467
+            if ($shouldEncrypt === true && $encryptionModule !== null) {
468
+                $headerSize = $this->getHeaderSize($path);
469
+                $source = $this->storage->fopen($path, $mode);
470
+                if (!is_resource($source)) {
471
+                    return false;
472
+                }
473
+                $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
474
+                    $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
475
+                    $size, $unencryptedSize, $headerSize, $signed);
476
+                return $handle;
477
+            }
478
+        }
479
+
480
+        return $this->storage->fopen($path, $mode);
481
+    }
482
+
483
+
484
+    /**
485
+     * perform some plausibility checks if the the unencrypted size is correct.
486
+     * If not, we calculate the correct unencrypted size and return it
487
+     *
488
+     * @param string $path internal path relative to the storage root
489
+     * @param int $unencryptedSize size of the unencrypted file
490
+     *
491
+     * @return int unencrypted size
492
+     */
493
+    protected function verifyUnencryptedSize($path, $unencryptedSize) {
494
+        $size = $this->storage->filesize($path);
495
+        $result = $unencryptedSize;
496
+
497
+        if ($unencryptedSize < 0 ||
498
+            ($size > 0 && $unencryptedSize === $size)
499
+        ) {
500
+            // check if we already calculate the unencrypted size for the
501
+            // given path to avoid recursions
502
+            if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
503
+                $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
504
+                try {
505
+                    $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
506
+                } catch (\Exception $e) {
507
+                    $this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path);
508
+                    $this->logger->logException($e);
509
+                }
510
+                unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
511
+            }
512
+        }
513
+
514
+        return $result;
515
+    }
516
+
517
+    /**
518
+     * calculate the unencrypted size
519
+     *
520
+     * @param string $path internal path relative to the storage root
521
+     * @param int $size size of the physical file
522
+     * @param int $unencryptedSize size of the unencrypted file
523
+     *
524
+     * @return int calculated unencrypted size
525
+     */
526
+    protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
527
+        $headerSize = $this->getHeaderSize($path);
528
+        $header = $this->getHeader($path);
529
+        $encryptionModule = $this->getEncryptionModule($path);
530
+
531
+        $stream = $this->storage->fopen($path, 'r');
532
+
533
+        // if we couldn't open the file we return the old unencrypted size
534
+        if (!is_resource($stream)) {
535
+            $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
536
+            return $unencryptedSize;
537
+        }
538
+
539
+        $newUnencryptedSize = 0;
540
+        $size -= $headerSize;
541
+        $blockSize = $this->util->getBlockSize();
542
+
543
+        // if a header exists we skip it
544
+        if ($headerSize > 0) {
545
+            fread($stream, $headerSize);
546
+        }
547
+
548
+        // fast path, else the calculation for $lastChunkNr is bogus
549
+        if ($size === 0) {
550
+            return 0;
551
+        }
552
+
553
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
554
+        $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
555
+
556
+        // calculate last chunk nr
557
+        // next highest is end of chunks, one subtracted is last one
558
+        // we have to read the last chunk, we can't just calculate it (because of padding etc)
559
+
560
+        $lastChunkNr = ceil($size / $blockSize) - 1;
561
+        // calculate last chunk position
562
+        $lastChunkPos = ($lastChunkNr * $blockSize);
563
+        // try to fseek to the last chunk, if it fails we have to read the whole file
564
+        if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
565
+            $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
566
+        }
567
+
568
+        $lastChunkContentEncrypted = '';
569
+        $count = $blockSize;
570
+
571
+        while ($count > 0) {
572
+            $data = fread($stream, $blockSize);
573
+            $count = strlen($data);
574
+            $lastChunkContentEncrypted .= $data;
575
+            if (strlen($lastChunkContentEncrypted) > $blockSize) {
576
+                $newUnencryptedSize += $unencryptedBlockSize;
577
+                $lastChunkContentEncrypted = substr($lastChunkContentEncrypted, $blockSize);
578
+            }
579
+        }
580
+
581
+        fclose($stream);
582
+
583
+        // we have to decrypt the last chunk to get it actual size
584
+        $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
585
+        $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
586
+        $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
587
+
588
+        // calc the real file size with the size of the last chunk
589
+        $newUnencryptedSize += strlen($decryptedLastChunk);
590
+
591
+        $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
592
+
593
+        // write to cache if applicable
594
+        $cache = $this->storage->getCache();
595
+        if ($cache) {
596
+            $entry = $cache->get($path);
597
+            $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
598
+        }
599
+
600
+        return $newUnencryptedSize;
601
+    }
602
+
603
+    /**
604
+     * @param Storage\IStorage $sourceStorage
605
+     * @param string $sourceInternalPath
606
+     * @param string $targetInternalPath
607
+     * @param bool $preserveMtime
608
+     * @return bool
609
+     */
610
+    public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
611
+        if ($sourceStorage === $this) {
612
+            return $this->rename($sourceInternalPath, $targetInternalPath);
613
+        }
614
+
615
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
616
+        // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
617
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
618
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
619
+        // - remove $this->copyBetweenStorage
620
+
621
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
622
+            return false;
623
+        }
624
+
625
+        $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
626
+        if ($result) {
627
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
628
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
629
+            } else {
630
+                $result &= $sourceStorage->unlink($sourceInternalPath);
631
+            }
632
+        }
633
+        return $result;
634
+    }
635
+
636
+
637
+    /**
638
+     * @param Storage\IStorage $sourceStorage
639
+     * @param string $sourceInternalPath
640
+     * @param string $targetInternalPath
641
+     * @param bool $preserveMtime
642
+     * @param bool $isRename
643
+     * @return bool
644
+     */
645
+    public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
646
+
647
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
648
+        // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
649
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
650
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
651
+        // - remove $this->copyBetweenStorage
652
+
653
+        return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
654
+    }
655
+
656
+    /**
657
+     * Update the encrypted cache version in the database
658
+     *
659
+     * @param Storage\IStorage $sourceStorage
660
+     * @param string $sourceInternalPath
661
+     * @param string $targetInternalPath
662
+     * @param bool $isRename
663
+     * @param bool $keepEncryptionVersion
664
+     */
665
+    private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
666
+        $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
667
+        $cacheInformation = [
668
+            'encrypted' => $isEncrypted,
669
+        ];
670
+        if ($isEncrypted) {
671
+            $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
672
+
673
+            // In case of a move operation from an unencrypted to an encrypted
674
+            // storage the old encrypted version would stay with "0" while the
675
+            // correct value would be "1". Thus we manually set the value to "1"
676
+            // for those cases.
677
+            // See also https://github.com/owncloud/core/issues/23078
678
+            if ($encryptedVersion === 0 || !$keepEncryptionVersion) {
679
+                $encryptedVersion = 1;
680
+            }
681
+
682
+            $cacheInformation['encryptedVersion'] = $encryptedVersion;
683
+        }
684
+
685
+        // in case of a rename we need to manipulate the source cache because
686
+        // this information will be kept for the new target
687
+        if ($isRename) {
688
+            $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
689
+        } else {
690
+            $this->getCache()->put($targetInternalPath, $cacheInformation);
691
+        }
692
+    }
693
+
694
+    /**
695
+     * copy file between two storages
696
+     *
697
+     * @param Storage\IStorage $sourceStorage
698
+     * @param string $sourceInternalPath
699
+     * @param string $targetInternalPath
700
+     * @param bool $preserveMtime
701
+     * @param bool $isRename
702
+     * @return bool
703
+     * @throws \Exception
704
+     */
705
+    private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
706
+
707
+        // for versions we have nothing to do, because versions should always use the
708
+        // key from the original file. Just create a 1:1 copy and done
709
+        if ($this->isVersion($targetInternalPath) ||
710
+            $this->isVersion($sourceInternalPath)) {
711
+            // remember that we try to create a version so that we can detect it during
712
+            // fopen($sourceInternalPath) and by-pass the encryption in order to
713
+            // create a 1:1 copy of the file
714
+            $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
715
+            $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
716
+            $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
717
+            if ($result) {
718
+                $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
719
+                // make sure that we update the unencrypted size for the version
720
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
721
+                    $this->updateUnencryptedSize(
722
+                        $this->getFullPath($targetInternalPath),
723
+                        $info['size']
724
+                    );
725
+                }
726
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
727
+            }
728
+            return $result;
729
+        }
730
+
731
+        // first copy the keys that we reuse the existing file key on the target location
732
+        // and don't create a new one which would break versions for example.
733
+        $mount = $this->mountManager->findByStorageId($sourceStorage->getId());
734
+        if (count($mount) === 1) {
735
+            $mountPoint = $mount[0]->getMountPoint();
736
+            $source = $mountPoint . '/' . $sourceInternalPath;
737
+            $target = $this->getFullPath($targetInternalPath);
738
+            $this->copyKeys($source, $target);
739
+        } else {
740
+            $this->logger->error('Could not find mount point, can\'t keep encryption keys');
741
+        }
742
+
743
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
744
+            $dh = $sourceStorage->opendir($sourceInternalPath);
745
+            $result = $this->mkdir($targetInternalPath);
746
+            if (is_resource($dh)) {
747
+                while ($result and ($file = readdir($dh)) !== false) {
748
+                    if (!Filesystem::isIgnoredDir($file)) {
749
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
750
+                    }
751
+                }
752
+            }
753
+        } else {
754
+            try {
755
+                $source = $sourceStorage->fopen($sourceInternalPath, 'r');
756
+                $target = $this->fopen($targetInternalPath, 'w');
757
+                [, $result] = \OC_Helper::streamCopy($source, $target);
758
+                fclose($source);
759
+                fclose($target);
760
+            } catch (\Exception $e) {
761
+                fclose($source);
762
+                fclose($target);
763
+                throw $e;
764
+            }
765
+            if ($result) {
766
+                if ($preserveMtime) {
767
+                    $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
768
+                }
769
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
770
+            } else {
771
+                // delete partially written target file
772
+                $this->unlink($targetInternalPath);
773
+                // delete cache entry that was created by fopen
774
+                $this->getCache()->remove($targetInternalPath);
775
+            }
776
+        }
777
+        return (bool)$result;
778
+    }
779
+
780
+    /**
781
+     * get the path to a local version of the file.
782
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
783
+     *
784
+     * @param string $path
785
+     * @return string
786
+     */
787
+    public function getLocalFile($path) {
788
+        if ($this->encryptionManager->isEnabled()) {
789
+            $cachedFile = $this->getCachedFile($path);
790
+            if (is_string($cachedFile)) {
791
+                return $cachedFile;
792
+            }
793
+        }
794
+        return $this->storage->getLocalFile($path);
795
+    }
796
+
797
+    /**
798
+     * Returns the wrapped storage's value for isLocal()
799
+     *
800
+     * @return bool wrapped storage's isLocal() value
801
+     */
802
+    public function isLocal() {
803
+        if ($this->encryptionManager->isEnabled()) {
804
+            return false;
805
+        }
806
+        return $this->storage->isLocal();
807
+    }
808
+
809
+    /**
810
+     * see https://www.php.net/manual/en/function.stat.php
811
+     * only the following keys are required in the result: size and mtime
812
+     *
813
+     * @param string $path
814
+     * @return array
815
+     */
816
+    public function stat($path) {
817
+        $stat = $this->storage->stat($path);
818
+        $fileSize = $this->filesize($path);
819
+        $stat['size'] = $fileSize;
820
+        $stat[7] = $fileSize;
821
+        $stat['hasHeader'] = $this->getHeaderSize($path) > 0;
822
+        return $stat;
823
+    }
824
+
825
+    /**
826
+     * see https://www.php.net/manual/en/function.hash.php
827
+     *
828
+     * @param string $type
829
+     * @param string $path
830
+     * @param bool $raw
831
+     * @return string
832
+     */
833
+    public function hash($type, $path, $raw = false) {
834
+        $fh = $this->fopen($path, 'rb');
835
+        $ctx = hash_init($type);
836
+        hash_update_stream($ctx, $fh);
837
+        fclose($fh);
838
+        return hash_final($ctx, $raw);
839
+    }
840
+
841
+    /**
842
+     * return full path, including mount point
843
+     *
844
+     * @param string $path relative to mount point
845
+     * @return string full path including mount point
846
+     */
847
+    protected function getFullPath($path) {
848
+        return Filesystem::normalizePath($this->mountPoint . '/' . $path);
849
+    }
850
+
851
+    /**
852
+     * read first block of encrypted file, typically this will contain the
853
+     * encryption header
854
+     *
855
+     * @param string $path
856
+     * @return string
857
+     */
858
+    protected function readFirstBlock($path) {
859
+        $firstBlock = '';
860
+        if ($this->storage->file_exists($path)) {
861
+            $handle = $this->storage->fopen($path, 'r');
862
+            $firstBlock = fread($handle, $this->util->getHeaderSize());
863
+            fclose($handle);
864
+        }
865
+        return $firstBlock;
866
+    }
867
+
868
+    /**
869
+     * return header size of given file
870
+     *
871
+     * @param string $path
872
+     * @return int
873
+     */
874
+    protected function getHeaderSize($path) {
875
+        $headerSize = 0;
876
+        $realFile = $this->util->stripPartialFileExtension($path);
877
+        if ($this->storage->file_exists($realFile)) {
878
+            $path = $realFile;
879
+        }
880
+        $firstBlock = $this->readFirstBlock($path);
881
+
882
+        if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
883
+            $headerSize = $this->util->getHeaderSize();
884
+        }
885
+
886
+        return $headerSize;
887
+    }
888
+
889
+    /**
890
+     * parse raw header to array
891
+     *
892
+     * @param string $rawHeader
893
+     * @return array
894
+     */
895
+    protected function parseRawHeader($rawHeader) {
896
+        $result = [];
897
+        if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
898
+            $header = $rawHeader;
899
+            $endAt = strpos($header, Util::HEADER_END);
900
+            if ($endAt !== false) {
901
+                $header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
902
+
903
+                // +1 to not start with an ':' which would result in empty element at the beginning
904
+                $exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
905
+
906
+                $element = array_shift($exploded);
907
+                while ($element !== Util::HEADER_END) {
908
+                    $result[$element] = array_shift($exploded);
909
+                    $element = array_shift($exploded);
910
+                }
911
+            }
912
+        }
913
+
914
+        return $result;
915
+    }
916
+
917
+    /**
918
+     * read header from file
919
+     *
920
+     * @param string $path
921
+     * @return array
922
+     */
923
+    protected function getHeader($path) {
924
+        $realFile = $this->util->stripPartialFileExtension($path);
925
+        $exists = $this->storage->file_exists($realFile);
926
+        if ($exists) {
927
+            $path = $realFile;
928
+        }
929
+
930
+        $firstBlock = $this->readFirstBlock($path);
931
+        $result = $this->parseRawHeader($firstBlock);
932
+
933
+        // if the header doesn't contain a encryption module we check if it is a
934
+        // legacy file. If true, we add the default encryption module
935
+        if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
936
+            if (!empty($result)) {
937
+                $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
938
+            } elseif ($exists) {
939
+                // if the header was empty we have to check first if it is a encrypted file at all
940
+                // We would do query to filecache only if we know that entry in filecache exists
941
+                $info = $this->getCache()->get($path);
942
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
943
+                    $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
944
+                }
945
+            }
946
+        }
947
+
948
+        return $result;
949
+    }
950
+
951
+    /**
952
+     * read encryption module needed to read/write the file located at $path
953
+     *
954
+     * @param string $path
955
+     * @return null|\OCP\Encryption\IEncryptionModule
956
+     * @throws ModuleDoesNotExistsException
957
+     * @throws \Exception
958
+     */
959
+    protected function getEncryptionModule($path) {
960
+        $encryptionModule = null;
961
+        $header = $this->getHeader($path);
962
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
963
+        if (!empty($encryptionModuleId)) {
964
+            try {
965
+                $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
966
+            } catch (ModuleDoesNotExistsException $e) {
967
+                $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
968
+                throw $e;
969
+            }
970
+        }
971
+
972
+        return $encryptionModule;
973
+    }
974
+
975
+    /**
976
+     * @param string $path
977
+     * @param int $unencryptedSize
978
+     */
979
+    public function updateUnencryptedSize($path, $unencryptedSize) {
980
+        $this->unencryptedSize[$path] = $unencryptedSize;
981
+    }
982
+
983
+    /**
984
+     * copy keys to new location
985
+     *
986
+     * @param string $source path relative to data/
987
+     * @param string $target path relative to data/
988
+     * @return bool
989
+     */
990
+    protected function copyKeys($source, $target) {
991
+        if (!$this->util->isExcluded($source)) {
992
+            return $this->keyStorage->copyKeys($source, $target);
993
+        }
994
+
995
+        return false;
996
+    }
997
+
998
+    /**
999
+     * check if path points to a files version
1000
+     *
1001
+     * @param $path
1002
+     * @return bool
1003
+     */
1004
+    protected function isVersion($path) {
1005
+        $normalized = Filesystem::normalizePath($path);
1006
+        return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1007
+    }
1008
+
1009
+    /**
1010
+     * check if the given storage should be encrypted or not
1011
+     *
1012
+     * @param $path
1013
+     * @return bool
1014
+     */
1015
+    protected function shouldEncrypt($path) {
1016
+        $fullPath = $this->getFullPath($path);
1017
+        $mountPointConfig = $this->mount->getOption('encrypt', true);
1018
+        if ($mountPointConfig === false) {
1019
+            return false;
1020
+        }
1021
+
1022
+        try {
1023
+            $encryptionModule = $this->getEncryptionModule($fullPath);
1024
+        } catch (ModuleDoesNotExistsException $e) {
1025
+            return false;
1026
+        }
1027
+
1028
+        if ($encryptionModule === null) {
1029
+            $encryptionModule = $this->encryptionManager->getEncryptionModule();
1030
+        }
1031
+
1032
+        return $encryptionModule->shouldEncrypt($fullPath);
1033
+    }
1034
+
1035
+    public function writeStream(string $path, $stream, int $size = null): int {
1036
+        // always fall back to fopen
1037
+        $target = $this->fopen($path, 'w');
1038
+        [$count, $result] = \OC_Helper::streamCopy($stream, $target);
1039
+        fclose($target);
1040
+        return $count;
1041
+    }
1042 1042
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Jail.php 1 patch
Indentation   +500 added lines, -500 removed lines patch added patch discarded remove patch
@@ -39,504 +39,504 @@
 block discarded – undo
39 39
  * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage
40 40
  */
41 41
 class Jail extends Wrapper {
42
-	/**
43
-	 * @var string
44
-	 */
45
-	protected $rootPath;
46
-
47
-	/**
48
-	 * @param array $arguments ['storage' => $storage, 'mask' => $root]
49
-	 *
50
-	 * $storage: The storage that will be wrapper
51
-	 * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
52
-	 */
53
-	public function __construct($arguments) {
54
-		parent::__construct($arguments);
55
-		$this->rootPath = $arguments['root'];
56
-	}
57
-
58
-	public function getUnjailedPath($path) {
59
-		return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
60
-	}
61
-
62
-	/**
63
-	 * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
64
-	 */
65
-	public function getUnjailedStorage() {
66
-		return $this->storage;
67
-	}
68
-
69
-
70
-	public function getJailedPath($path) {
71
-		$root = rtrim($this->rootPath, '/') . '/';
72
-
73
-		if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
74
-			return null;
75
-		} else {
76
-			$path = substr($path, strlen($this->rootPath));
77
-			return trim($path, '/');
78
-		}
79
-	}
80
-
81
-	public function getId() {
82
-		return parent::getId();
83
-	}
84
-
85
-	/**
86
-	 * see https://www.php.net/manual/en/function.mkdir.php
87
-	 *
88
-	 * @param string $path
89
-	 * @return bool
90
-	 */
91
-	public function mkdir($path) {
92
-		return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
93
-	}
94
-
95
-	/**
96
-	 * see https://www.php.net/manual/en/function.rmdir.php
97
-	 *
98
-	 * @param string $path
99
-	 * @return bool
100
-	 */
101
-	public function rmdir($path) {
102
-		return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
103
-	}
104
-
105
-	/**
106
-	 * see https://www.php.net/manual/en/function.opendir.php
107
-	 *
108
-	 * @param string $path
109
-	 * @return resource
110
-	 */
111
-	public function opendir($path) {
112
-		return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
113
-	}
114
-
115
-	/**
116
-	 * see https://www.php.net/manual/en/function.is_dir.php
117
-	 *
118
-	 * @param string $path
119
-	 * @return bool
120
-	 */
121
-	public function is_dir($path) {
122
-		return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
123
-	}
124
-
125
-	/**
126
-	 * see https://www.php.net/manual/en/function.is_file.php
127
-	 *
128
-	 * @param string $path
129
-	 * @return bool
130
-	 */
131
-	public function is_file($path) {
132
-		return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
133
-	}
134
-
135
-	/**
136
-	 * see https://www.php.net/manual/en/function.stat.php
137
-	 * only the following keys are required in the result: size and mtime
138
-	 *
139
-	 * @param string $path
140
-	 * @return array
141
-	 */
142
-	public function stat($path) {
143
-		return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
144
-	}
145
-
146
-	/**
147
-	 * see https://www.php.net/manual/en/function.filetype.php
148
-	 *
149
-	 * @param string $path
150
-	 * @return bool
151
-	 */
152
-	public function filetype($path) {
153
-		return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
154
-	}
155
-
156
-	/**
157
-	 * see https://www.php.net/manual/en/function.filesize.php
158
-	 * The result for filesize when called on a folder is required to be 0
159
-	 *
160
-	 * @param string $path
161
-	 * @return int
162
-	 */
163
-	public function filesize($path) {
164
-		return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
165
-	}
166
-
167
-	/**
168
-	 * check if a file can be created in $path
169
-	 *
170
-	 * @param string $path
171
-	 * @return bool
172
-	 */
173
-	public function isCreatable($path) {
174
-		return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
175
-	}
176
-
177
-	/**
178
-	 * check if a file can be read
179
-	 *
180
-	 * @param string $path
181
-	 * @return bool
182
-	 */
183
-	public function isReadable($path) {
184
-		return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
185
-	}
186
-
187
-	/**
188
-	 * check if a file can be written to
189
-	 *
190
-	 * @param string $path
191
-	 * @return bool
192
-	 */
193
-	public function isUpdatable($path) {
194
-		return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
195
-	}
196
-
197
-	/**
198
-	 * check if a file can be deleted
199
-	 *
200
-	 * @param string $path
201
-	 * @return bool
202
-	 */
203
-	public function isDeletable($path) {
204
-		return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
205
-	}
206
-
207
-	/**
208
-	 * check if a file can be shared
209
-	 *
210
-	 * @param string $path
211
-	 * @return bool
212
-	 */
213
-	public function isSharable($path) {
214
-		return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
215
-	}
216
-
217
-	/**
218
-	 * get the full permissions of a path.
219
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
220
-	 *
221
-	 * @param string $path
222
-	 * @return int
223
-	 */
224
-	public function getPermissions($path) {
225
-		return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
226
-	}
227
-
228
-	/**
229
-	 * see https://www.php.net/manual/en/function.file_exists.php
230
-	 *
231
-	 * @param string $path
232
-	 * @return bool
233
-	 */
234
-	public function file_exists($path) {
235
-		return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
236
-	}
237
-
238
-	/**
239
-	 * see https://www.php.net/manual/en/function.filemtime.php
240
-	 *
241
-	 * @param string $path
242
-	 * @return int
243
-	 */
244
-	public function filemtime($path) {
245
-		return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
246
-	}
247
-
248
-	/**
249
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
250
-	 *
251
-	 * @param string $path
252
-	 * @return string
253
-	 */
254
-	public function file_get_contents($path) {
255
-		return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
256
-	}
257
-
258
-	/**
259
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
260
-	 *
261
-	 * @param string $path
262
-	 * @param mixed $data
263
-	 * @return int|false
264
-	 */
265
-	public function file_put_contents($path, $data) {
266
-		return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
267
-	}
268
-
269
-	/**
270
-	 * see https://www.php.net/manual/en/function.unlink.php
271
-	 *
272
-	 * @param string $path
273
-	 * @return bool
274
-	 */
275
-	public function unlink($path) {
276
-		return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
277
-	}
278
-
279
-	/**
280
-	 * see https://www.php.net/manual/en/function.rename.php
281
-	 *
282
-	 * @param string $path1
283
-	 * @param string $path2
284
-	 * @return bool
285
-	 */
286
-	public function rename($path1, $path2) {
287
-		return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
288
-	}
289
-
290
-	/**
291
-	 * see https://www.php.net/manual/en/function.copy.php
292
-	 *
293
-	 * @param string $path1
294
-	 * @param string $path2
295
-	 * @return bool
296
-	 */
297
-	public function copy($path1, $path2) {
298
-		return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
299
-	}
300
-
301
-	/**
302
-	 * see https://www.php.net/manual/en/function.fopen.php
303
-	 *
304
-	 * @param string $path
305
-	 * @param string $mode
306
-	 * @return resource
307
-	 */
308
-	public function fopen($path, $mode) {
309
-		return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
310
-	}
311
-
312
-	/**
313
-	 * get the mimetype for a file or folder
314
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
315
-	 *
316
-	 * @param string $path
317
-	 * @return string
318
-	 */
319
-	public function getMimeType($path) {
320
-		return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
321
-	}
322
-
323
-	/**
324
-	 * see https://www.php.net/manual/en/function.hash.php
325
-	 *
326
-	 * @param string $type
327
-	 * @param string $path
328
-	 * @param bool $raw
329
-	 * @return string
330
-	 */
331
-	public function hash($type, $path, $raw = false) {
332
-		return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
333
-	}
334
-
335
-	/**
336
-	 * see https://www.php.net/manual/en/function.free_space.php
337
-	 *
338
-	 * @param string $path
339
-	 * @return int
340
-	 */
341
-	public function free_space($path) {
342
-		return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
343
-	}
344
-
345
-	/**
346
-	 * search for occurrences of $query in file names
347
-	 *
348
-	 * @param string $query
349
-	 * @return array
350
-	 */
351
-	public function search($query) {
352
-		return $this->getWrapperStorage()->search($query);
353
-	}
354
-
355
-	/**
356
-	 * see https://www.php.net/manual/en/function.touch.php
357
-	 * If the backend does not support the operation, false should be returned
358
-	 *
359
-	 * @param string $path
360
-	 * @param int $mtime
361
-	 * @return bool
362
-	 */
363
-	public function touch($path, $mtime = null) {
364
-		return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
365
-	}
366
-
367
-	/**
368
-	 * get the path to a local version of the file.
369
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
370
-	 *
371
-	 * @param string $path
372
-	 * @return string
373
-	 */
374
-	public function getLocalFile($path) {
375
-		return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
376
-	}
377
-
378
-	/**
379
-	 * check if a file or folder has been updated since $time
380
-	 *
381
-	 * @param string $path
382
-	 * @param int $time
383
-	 * @return bool
384
-	 *
385
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
386
-	 * returning true for other changes in the folder is optional
387
-	 */
388
-	public function hasUpdated($path, $time) {
389
-		return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
390
-	}
391
-
392
-	/**
393
-	 * get a cache instance for the storage
394
-	 *
395
-	 * @param string $path
396
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
397
-	 * @return \OC\Files\Cache\Cache
398
-	 */
399
-	public function getCache($path = '', $storage = null) {
400
-		if (!$storage) {
401
-			$storage = $this->getWrapperStorage();
402
-		}
403
-		$sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
404
-		return new CacheJail($sourceCache, $this->rootPath);
405
-	}
406
-
407
-	/**
408
-	 * get the user id of the owner of a file or folder
409
-	 *
410
-	 * @param string $path
411
-	 * @return string
412
-	 */
413
-	public function getOwner($path) {
414
-		return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
415
-	}
416
-
417
-	/**
418
-	 * get a watcher instance for the cache
419
-	 *
420
-	 * @param string $path
421
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
422
-	 * @return \OC\Files\Cache\Watcher
423
-	 */
424
-	public function getWatcher($path = '', $storage = null) {
425
-		if (!$storage) {
426
-			$storage = $this;
427
-		}
428
-		return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
429
-	}
430
-
431
-	/**
432
-	 * get the ETag for a file or folder
433
-	 *
434
-	 * @param string $path
435
-	 * @return string
436
-	 */
437
-	public function getETag($path) {
438
-		return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
439
-	}
440
-
441
-	/**
442
-	 * @param string $path
443
-	 * @return array
444
-	 */
445
-	public function getMetaData($path) {
446
-		return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
447
-	}
448
-
449
-	/**
450
-	 * @param string $path
451
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
452
-	 * @param \OCP\Lock\ILockingProvider $provider
453
-	 * @throws \OCP\Lock\LockedException
454
-	 */
455
-	public function acquireLock($path, $type, ILockingProvider $provider) {
456
-		$this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
457
-	}
458
-
459
-	/**
460
-	 * @param string $path
461
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
462
-	 * @param \OCP\Lock\ILockingProvider $provider
463
-	 */
464
-	public function releaseLock($path, $type, ILockingProvider $provider) {
465
-		$this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
466
-	}
467
-
468
-	/**
469
-	 * @param string $path
470
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
471
-	 * @param \OCP\Lock\ILockingProvider $provider
472
-	 */
473
-	public function changeLock($path, $type, ILockingProvider $provider) {
474
-		$this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
475
-	}
476
-
477
-	/**
478
-	 * Resolve the path for the source of the share
479
-	 *
480
-	 * @param string $path
481
-	 * @return array
482
-	 */
483
-	public function resolvePath($path) {
484
-		return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
485
-	}
486
-
487
-	/**
488
-	 * @param IStorage $sourceStorage
489
-	 * @param string $sourceInternalPath
490
-	 * @param string $targetInternalPath
491
-	 * @return bool
492
-	 */
493
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
494
-		if ($sourceStorage === $this) {
495
-			return $this->copy($sourceInternalPath, $targetInternalPath);
496
-		}
497
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
498
-	}
499
-
500
-	/**
501
-	 * @param IStorage $sourceStorage
502
-	 * @param string $sourceInternalPath
503
-	 * @param string $targetInternalPath
504
-	 * @return bool
505
-	 */
506
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
507
-		if ($sourceStorage === $this) {
508
-			return $this->rename($sourceInternalPath, $targetInternalPath);
509
-		}
510
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
511
-	}
512
-
513
-	public function getPropagator($storage = null) {
514
-		if (isset($this->propagator)) {
515
-			return $this->propagator;
516
-		}
517
-
518
-		if (!$storage) {
519
-			$storage = $this;
520
-		}
521
-		$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
522
-		return $this->propagator;
523
-	}
524
-
525
-	public function writeStream(string $path, $stream, int $size = null): int {
526
-		$storage = $this->getWrapperStorage();
527
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
528
-			/** @var IWriteStreamStorage $storage */
529
-			return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
530
-		} else {
531
-			$target = $this->fopen($path, 'w');
532
-			list($count, $result) = \OC_Helper::streamCopy($stream, $target);
533
-			fclose($stream);
534
-			fclose($target);
535
-			return $count;
536
-		}
537
-	}
538
-
539
-	public function getDirectoryContent($directory): \Traversable {
540
-		return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
541
-	}
42
+    /**
43
+     * @var string
44
+     */
45
+    protected $rootPath;
46
+
47
+    /**
48
+     * @param array $arguments ['storage' => $storage, 'mask' => $root]
49
+     *
50
+     * $storage: The storage that will be wrapper
51
+     * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
52
+     */
53
+    public function __construct($arguments) {
54
+        parent::__construct($arguments);
55
+        $this->rootPath = $arguments['root'];
56
+    }
57
+
58
+    public function getUnjailedPath($path) {
59
+        return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
60
+    }
61
+
62
+    /**
63
+     * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
64
+     */
65
+    public function getUnjailedStorage() {
66
+        return $this->storage;
67
+    }
68
+
69
+
70
+    public function getJailedPath($path) {
71
+        $root = rtrim($this->rootPath, '/') . '/';
72
+
73
+        if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
74
+            return null;
75
+        } else {
76
+            $path = substr($path, strlen($this->rootPath));
77
+            return trim($path, '/');
78
+        }
79
+    }
80
+
81
+    public function getId() {
82
+        return parent::getId();
83
+    }
84
+
85
+    /**
86
+     * see https://www.php.net/manual/en/function.mkdir.php
87
+     *
88
+     * @param string $path
89
+     * @return bool
90
+     */
91
+    public function mkdir($path) {
92
+        return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
93
+    }
94
+
95
+    /**
96
+     * see https://www.php.net/manual/en/function.rmdir.php
97
+     *
98
+     * @param string $path
99
+     * @return bool
100
+     */
101
+    public function rmdir($path) {
102
+        return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
103
+    }
104
+
105
+    /**
106
+     * see https://www.php.net/manual/en/function.opendir.php
107
+     *
108
+     * @param string $path
109
+     * @return resource
110
+     */
111
+    public function opendir($path) {
112
+        return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
113
+    }
114
+
115
+    /**
116
+     * see https://www.php.net/manual/en/function.is_dir.php
117
+     *
118
+     * @param string $path
119
+     * @return bool
120
+     */
121
+    public function is_dir($path) {
122
+        return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
123
+    }
124
+
125
+    /**
126
+     * see https://www.php.net/manual/en/function.is_file.php
127
+     *
128
+     * @param string $path
129
+     * @return bool
130
+     */
131
+    public function is_file($path) {
132
+        return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
133
+    }
134
+
135
+    /**
136
+     * see https://www.php.net/manual/en/function.stat.php
137
+     * only the following keys are required in the result: size and mtime
138
+     *
139
+     * @param string $path
140
+     * @return array
141
+     */
142
+    public function stat($path) {
143
+        return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
144
+    }
145
+
146
+    /**
147
+     * see https://www.php.net/manual/en/function.filetype.php
148
+     *
149
+     * @param string $path
150
+     * @return bool
151
+     */
152
+    public function filetype($path) {
153
+        return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
154
+    }
155
+
156
+    /**
157
+     * see https://www.php.net/manual/en/function.filesize.php
158
+     * The result for filesize when called on a folder is required to be 0
159
+     *
160
+     * @param string $path
161
+     * @return int
162
+     */
163
+    public function filesize($path) {
164
+        return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
165
+    }
166
+
167
+    /**
168
+     * check if a file can be created in $path
169
+     *
170
+     * @param string $path
171
+     * @return bool
172
+     */
173
+    public function isCreatable($path) {
174
+        return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
175
+    }
176
+
177
+    /**
178
+     * check if a file can be read
179
+     *
180
+     * @param string $path
181
+     * @return bool
182
+     */
183
+    public function isReadable($path) {
184
+        return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
185
+    }
186
+
187
+    /**
188
+     * check if a file can be written to
189
+     *
190
+     * @param string $path
191
+     * @return bool
192
+     */
193
+    public function isUpdatable($path) {
194
+        return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
195
+    }
196
+
197
+    /**
198
+     * check if a file can be deleted
199
+     *
200
+     * @param string $path
201
+     * @return bool
202
+     */
203
+    public function isDeletable($path) {
204
+        return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
205
+    }
206
+
207
+    /**
208
+     * check if a file can be shared
209
+     *
210
+     * @param string $path
211
+     * @return bool
212
+     */
213
+    public function isSharable($path) {
214
+        return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
215
+    }
216
+
217
+    /**
218
+     * get the full permissions of a path.
219
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
220
+     *
221
+     * @param string $path
222
+     * @return int
223
+     */
224
+    public function getPermissions($path) {
225
+        return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
226
+    }
227
+
228
+    /**
229
+     * see https://www.php.net/manual/en/function.file_exists.php
230
+     *
231
+     * @param string $path
232
+     * @return bool
233
+     */
234
+    public function file_exists($path) {
235
+        return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
236
+    }
237
+
238
+    /**
239
+     * see https://www.php.net/manual/en/function.filemtime.php
240
+     *
241
+     * @param string $path
242
+     * @return int
243
+     */
244
+    public function filemtime($path) {
245
+        return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
246
+    }
247
+
248
+    /**
249
+     * see https://www.php.net/manual/en/function.file_get_contents.php
250
+     *
251
+     * @param string $path
252
+     * @return string
253
+     */
254
+    public function file_get_contents($path) {
255
+        return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
256
+    }
257
+
258
+    /**
259
+     * see https://www.php.net/manual/en/function.file_put_contents.php
260
+     *
261
+     * @param string $path
262
+     * @param mixed $data
263
+     * @return int|false
264
+     */
265
+    public function file_put_contents($path, $data) {
266
+        return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
267
+    }
268
+
269
+    /**
270
+     * see https://www.php.net/manual/en/function.unlink.php
271
+     *
272
+     * @param string $path
273
+     * @return bool
274
+     */
275
+    public function unlink($path) {
276
+        return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
277
+    }
278
+
279
+    /**
280
+     * see https://www.php.net/manual/en/function.rename.php
281
+     *
282
+     * @param string $path1
283
+     * @param string $path2
284
+     * @return bool
285
+     */
286
+    public function rename($path1, $path2) {
287
+        return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
288
+    }
289
+
290
+    /**
291
+     * see https://www.php.net/manual/en/function.copy.php
292
+     *
293
+     * @param string $path1
294
+     * @param string $path2
295
+     * @return bool
296
+     */
297
+    public function copy($path1, $path2) {
298
+        return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
299
+    }
300
+
301
+    /**
302
+     * see https://www.php.net/manual/en/function.fopen.php
303
+     *
304
+     * @param string $path
305
+     * @param string $mode
306
+     * @return resource
307
+     */
308
+    public function fopen($path, $mode) {
309
+        return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
310
+    }
311
+
312
+    /**
313
+     * get the mimetype for a file or folder
314
+     * The mimetype for a folder is required to be "httpd/unix-directory"
315
+     *
316
+     * @param string $path
317
+     * @return string
318
+     */
319
+    public function getMimeType($path) {
320
+        return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
321
+    }
322
+
323
+    /**
324
+     * see https://www.php.net/manual/en/function.hash.php
325
+     *
326
+     * @param string $type
327
+     * @param string $path
328
+     * @param bool $raw
329
+     * @return string
330
+     */
331
+    public function hash($type, $path, $raw = false) {
332
+        return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
333
+    }
334
+
335
+    /**
336
+     * see https://www.php.net/manual/en/function.free_space.php
337
+     *
338
+     * @param string $path
339
+     * @return int
340
+     */
341
+    public function free_space($path) {
342
+        return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
343
+    }
344
+
345
+    /**
346
+     * search for occurrences of $query in file names
347
+     *
348
+     * @param string $query
349
+     * @return array
350
+     */
351
+    public function search($query) {
352
+        return $this->getWrapperStorage()->search($query);
353
+    }
354
+
355
+    /**
356
+     * see https://www.php.net/manual/en/function.touch.php
357
+     * If the backend does not support the operation, false should be returned
358
+     *
359
+     * @param string $path
360
+     * @param int $mtime
361
+     * @return bool
362
+     */
363
+    public function touch($path, $mtime = null) {
364
+        return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
365
+    }
366
+
367
+    /**
368
+     * get the path to a local version of the file.
369
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
370
+     *
371
+     * @param string $path
372
+     * @return string
373
+     */
374
+    public function getLocalFile($path) {
375
+        return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
376
+    }
377
+
378
+    /**
379
+     * check if a file or folder has been updated since $time
380
+     *
381
+     * @param string $path
382
+     * @param int $time
383
+     * @return bool
384
+     *
385
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
386
+     * returning true for other changes in the folder is optional
387
+     */
388
+    public function hasUpdated($path, $time) {
389
+        return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
390
+    }
391
+
392
+    /**
393
+     * get a cache instance for the storage
394
+     *
395
+     * @param string $path
396
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
397
+     * @return \OC\Files\Cache\Cache
398
+     */
399
+    public function getCache($path = '', $storage = null) {
400
+        if (!$storage) {
401
+            $storage = $this->getWrapperStorage();
402
+        }
403
+        $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
404
+        return new CacheJail($sourceCache, $this->rootPath);
405
+    }
406
+
407
+    /**
408
+     * get the user id of the owner of a file or folder
409
+     *
410
+     * @param string $path
411
+     * @return string
412
+     */
413
+    public function getOwner($path) {
414
+        return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
415
+    }
416
+
417
+    /**
418
+     * get a watcher instance for the cache
419
+     *
420
+     * @param string $path
421
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
422
+     * @return \OC\Files\Cache\Watcher
423
+     */
424
+    public function getWatcher($path = '', $storage = null) {
425
+        if (!$storage) {
426
+            $storage = $this;
427
+        }
428
+        return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
429
+    }
430
+
431
+    /**
432
+     * get the ETag for a file or folder
433
+     *
434
+     * @param string $path
435
+     * @return string
436
+     */
437
+    public function getETag($path) {
438
+        return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
439
+    }
440
+
441
+    /**
442
+     * @param string $path
443
+     * @return array
444
+     */
445
+    public function getMetaData($path) {
446
+        return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
447
+    }
448
+
449
+    /**
450
+     * @param string $path
451
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
452
+     * @param \OCP\Lock\ILockingProvider $provider
453
+     * @throws \OCP\Lock\LockedException
454
+     */
455
+    public function acquireLock($path, $type, ILockingProvider $provider) {
456
+        $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
457
+    }
458
+
459
+    /**
460
+     * @param string $path
461
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
462
+     * @param \OCP\Lock\ILockingProvider $provider
463
+     */
464
+    public function releaseLock($path, $type, ILockingProvider $provider) {
465
+        $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
466
+    }
467
+
468
+    /**
469
+     * @param string $path
470
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
471
+     * @param \OCP\Lock\ILockingProvider $provider
472
+     */
473
+    public function changeLock($path, $type, ILockingProvider $provider) {
474
+        $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
475
+    }
476
+
477
+    /**
478
+     * Resolve the path for the source of the share
479
+     *
480
+     * @param string $path
481
+     * @return array
482
+     */
483
+    public function resolvePath($path) {
484
+        return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
485
+    }
486
+
487
+    /**
488
+     * @param IStorage $sourceStorage
489
+     * @param string $sourceInternalPath
490
+     * @param string $targetInternalPath
491
+     * @return bool
492
+     */
493
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
494
+        if ($sourceStorage === $this) {
495
+            return $this->copy($sourceInternalPath, $targetInternalPath);
496
+        }
497
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
498
+    }
499
+
500
+    /**
501
+     * @param IStorage $sourceStorage
502
+     * @param string $sourceInternalPath
503
+     * @param string $targetInternalPath
504
+     * @return bool
505
+     */
506
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
507
+        if ($sourceStorage === $this) {
508
+            return $this->rename($sourceInternalPath, $targetInternalPath);
509
+        }
510
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
511
+    }
512
+
513
+    public function getPropagator($storage = null) {
514
+        if (isset($this->propagator)) {
515
+            return $this->propagator;
516
+        }
517
+
518
+        if (!$storage) {
519
+            $storage = $this;
520
+        }
521
+        $this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
522
+        return $this->propagator;
523
+    }
524
+
525
+    public function writeStream(string $path, $stream, int $size = null): int {
526
+        $storage = $this->getWrapperStorage();
527
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
528
+            /** @var IWriteStreamStorage $storage */
529
+            return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
530
+        } else {
531
+            $target = $this->fopen($path, 'w');
532
+            list($count, $result) = \OC_Helper::streamCopy($stream, $target);
533
+            fclose($stream);
534
+            fclose($target);
535
+            return $count;
536
+        }
537
+    }
538
+
539
+    public function getDirectoryContent($directory): \Traversable {
540
+        return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
541
+    }
542 542
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encoding.php 1 patch
Indentation   +502 added lines, -502 removed lines patch added patch discarded remove patch
@@ -37,506 +37,506 @@
 block discarded – undo
37 37
  */
38 38
 class Encoding extends Wrapper {
39 39
 
40
-	/**
41
-	 * @var ICache
42
-	 */
43
-	private $namesCache;
44
-
45
-	/**
46
-	 * @param array $parameters
47
-	 */
48
-	public function __construct($parameters) {
49
-		$this->storage = $parameters['storage'];
50
-		$this->namesCache = new CappedMemoryCache();
51
-	}
52
-
53
-	/**
54
-	 * Returns whether the given string is only made of ASCII characters
55
-	 *
56
-	 * @param string $str string
57
-	 *
58
-	 * @return bool true if the string is all ASCII, false otherwise
59
-	 */
60
-	private function isAscii($str) {
61
-		return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
62
-	}
63
-
64
-	/**
65
-	 * Checks whether the given path exists in NFC or NFD form after checking
66
-	 * each form for each path section and returns the correct form.
67
-	 * If no existing path found, returns the path as it was given.
68
-	 *
69
-	 * @param string $fullPath path to check
70
-	 *
71
-	 * @return string original or converted path
72
-	 */
73
-	private function findPathToUse($fullPath) {
74
-		$cachedPath = $this->namesCache[$fullPath];
75
-		if ($cachedPath !== null) {
76
-			return $cachedPath;
77
-		}
78
-
79
-		$sections = explode('/', $fullPath);
80
-		$path = '';
81
-		foreach ($sections as $section) {
82
-			$convertedPath = $this->findPathToUseLastSection($path, $section);
83
-			if ($convertedPath === null) {
84
-				// no point in continuing if the section was not found, use original path
85
-				return $fullPath;
86
-			}
87
-			$path = $convertedPath . '/';
88
-		}
89
-		$path = rtrim($path, '/');
90
-		return $path;
91
-	}
92
-
93
-	/**
94
-	 * Checks whether the last path section of the given path exists in NFC or NFD form
95
-	 * and returns the correct form. If no existing path found, returns null.
96
-	 *
97
-	 * @param string $basePath base path to check
98
-	 * @param string $lastSection last section of the path to check for NFD/NFC variations
99
-	 *
100
-	 * @return string|null original or converted path, or null if none of the forms was found
101
-	 */
102
-	private function findPathToUseLastSection($basePath, $lastSection) {
103
-		$fullPath = $basePath . $lastSection;
104
-		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
105
-			$this->namesCache[$fullPath] = $fullPath;
106
-			return $fullPath;
107
-		}
108
-
109
-		// swap encoding
110
-		if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
111
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
112
-		} else {
113
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
114
-		}
115
-		$otherFullPath = $basePath . $otherFormPath;
116
-		if ($this->storage->file_exists($otherFullPath)) {
117
-			$this->namesCache[$fullPath] = $otherFullPath;
118
-			return $otherFullPath;
119
-		}
120
-
121
-		// return original path, file did not exist at all
122
-		$this->namesCache[$fullPath] = $fullPath;
123
-		return null;
124
-	}
125
-
126
-	/**
127
-	 * see https://www.php.net/manual/en/function.mkdir.php
128
-	 *
129
-	 * @param string $path
130
-	 * @return bool
131
-	 */
132
-	public function mkdir($path) {
133
-		// note: no conversion here, method should not be called with non-NFC names!
134
-		$result = $this->storage->mkdir($path);
135
-		if ($result) {
136
-			$this->namesCache[$path] = $path;
137
-		}
138
-		return $result;
139
-	}
140
-
141
-	/**
142
-	 * see https://www.php.net/manual/en/function.rmdir.php
143
-	 *
144
-	 * @param string $path
145
-	 * @return bool
146
-	 */
147
-	public function rmdir($path) {
148
-		$result = $this->storage->rmdir($this->findPathToUse($path));
149
-		if ($result) {
150
-			unset($this->namesCache[$path]);
151
-		}
152
-		return $result;
153
-	}
154
-
155
-	/**
156
-	 * see https://www.php.net/manual/en/function.opendir.php
157
-	 *
158
-	 * @param string $path
159
-	 * @return resource
160
-	 */
161
-	public function opendir($path) {
162
-		return $this->storage->opendir($this->findPathToUse($path));
163
-	}
164
-
165
-	/**
166
-	 * see https://www.php.net/manual/en/function.is_dir.php
167
-	 *
168
-	 * @param string $path
169
-	 * @return bool
170
-	 */
171
-	public function is_dir($path) {
172
-		return $this->storage->is_dir($this->findPathToUse($path));
173
-	}
174
-
175
-	/**
176
-	 * see https://www.php.net/manual/en/function.is_file.php
177
-	 *
178
-	 * @param string $path
179
-	 * @return bool
180
-	 */
181
-	public function is_file($path) {
182
-		return $this->storage->is_file($this->findPathToUse($path));
183
-	}
184
-
185
-	/**
186
-	 * see https://www.php.net/manual/en/function.stat.php
187
-	 * only the following keys are required in the result: size and mtime
188
-	 *
189
-	 * @param string $path
190
-	 * @return array
191
-	 */
192
-	public function stat($path) {
193
-		return $this->storage->stat($this->findPathToUse($path));
194
-	}
195
-
196
-	/**
197
-	 * see https://www.php.net/manual/en/function.filetype.php
198
-	 *
199
-	 * @param string $path
200
-	 * @return bool
201
-	 */
202
-	public function filetype($path) {
203
-		return $this->storage->filetype($this->findPathToUse($path));
204
-	}
205
-
206
-	/**
207
-	 * see https://www.php.net/manual/en/function.filesize.php
208
-	 * The result for filesize when called on a folder is required to be 0
209
-	 *
210
-	 * @param string $path
211
-	 * @return int
212
-	 */
213
-	public function filesize($path) {
214
-		return $this->storage->filesize($this->findPathToUse($path));
215
-	}
216
-
217
-	/**
218
-	 * check if a file can be created in $path
219
-	 *
220
-	 * @param string $path
221
-	 * @return bool
222
-	 */
223
-	public function isCreatable($path) {
224
-		return $this->storage->isCreatable($this->findPathToUse($path));
225
-	}
226
-
227
-	/**
228
-	 * check if a file can be read
229
-	 *
230
-	 * @param string $path
231
-	 * @return bool
232
-	 */
233
-	public function isReadable($path) {
234
-		return $this->storage->isReadable($this->findPathToUse($path));
235
-	}
236
-
237
-	/**
238
-	 * check if a file can be written to
239
-	 *
240
-	 * @param string $path
241
-	 * @return bool
242
-	 */
243
-	public function isUpdatable($path) {
244
-		return $this->storage->isUpdatable($this->findPathToUse($path));
245
-	}
246
-
247
-	/**
248
-	 * check if a file can be deleted
249
-	 *
250
-	 * @param string $path
251
-	 * @return bool
252
-	 */
253
-	public function isDeletable($path) {
254
-		return $this->storage->isDeletable($this->findPathToUse($path));
255
-	}
256
-
257
-	/**
258
-	 * check if a file can be shared
259
-	 *
260
-	 * @param string $path
261
-	 * @return bool
262
-	 */
263
-	public function isSharable($path) {
264
-		return $this->storage->isSharable($this->findPathToUse($path));
265
-	}
266
-
267
-	/**
268
-	 * get the full permissions of a path.
269
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
270
-	 *
271
-	 * @param string $path
272
-	 * @return int
273
-	 */
274
-	public function getPermissions($path) {
275
-		return $this->storage->getPermissions($this->findPathToUse($path));
276
-	}
277
-
278
-	/**
279
-	 * see https://www.php.net/manual/en/function.file_exists.php
280
-	 *
281
-	 * @param string $path
282
-	 * @return bool
283
-	 */
284
-	public function file_exists($path) {
285
-		return $this->storage->file_exists($this->findPathToUse($path));
286
-	}
287
-
288
-	/**
289
-	 * see https://www.php.net/manual/en/function.filemtime.php
290
-	 *
291
-	 * @param string $path
292
-	 * @return int
293
-	 */
294
-	public function filemtime($path) {
295
-		return $this->storage->filemtime($this->findPathToUse($path));
296
-	}
297
-
298
-	/**
299
-	 * see https://www.php.net/manual/en/function.file_get_contents.php
300
-	 *
301
-	 * @param string $path
302
-	 * @return string
303
-	 */
304
-	public function file_get_contents($path) {
305
-		return $this->storage->file_get_contents($this->findPathToUse($path));
306
-	}
307
-
308
-	/**
309
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
310
-	 *
311
-	 * @param string $path
312
-	 * @param mixed $data
313
-	 * @return int|false
314
-	 */
315
-	public function file_put_contents($path, $data) {
316
-		return $this->storage->file_put_contents($this->findPathToUse($path), $data);
317
-	}
318
-
319
-	/**
320
-	 * see https://www.php.net/manual/en/function.unlink.php
321
-	 *
322
-	 * @param string $path
323
-	 * @return bool
324
-	 */
325
-	public function unlink($path) {
326
-		$result = $this->storage->unlink($this->findPathToUse($path));
327
-		if ($result) {
328
-			unset($this->namesCache[$path]);
329
-		}
330
-		return $result;
331
-	}
332
-
333
-	/**
334
-	 * see https://www.php.net/manual/en/function.rename.php
335
-	 *
336
-	 * @param string $path1
337
-	 * @param string $path2
338
-	 * @return bool
339
-	 */
340
-	public function rename($path1, $path2) {
341
-		// second name always NFC
342
-		return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
343
-	}
344
-
345
-	/**
346
-	 * see https://www.php.net/manual/en/function.copy.php
347
-	 *
348
-	 * @param string $path1
349
-	 * @param string $path2
350
-	 * @return bool
351
-	 */
352
-	public function copy($path1, $path2) {
353
-		return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
354
-	}
355
-
356
-	/**
357
-	 * see https://www.php.net/manual/en/function.fopen.php
358
-	 *
359
-	 * @param string $path
360
-	 * @param string $mode
361
-	 * @return resource
362
-	 */
363
-	public function fopen($path, $mode) {
364
-		$result = $this->storage->fopen($this->findPathToUse($path), $mode);
365
-		if ($result && $mode !== 'r' && $mode !== 'rb') {
366
-			unset($this->namesCache[$path]);
367
-		}
368
-		return $result;
369
-	}
370
-
371
-	/**
372
-	 * get the mimetype for a file or folder
373
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
374
-	 *
375
-	 * @param string $path
376
-	 * @return string
377
-	 */
378
-	public function getMimeType($path) {
379
-		return $this->storage->getMimeType($this->findPathToUse($path));
380
-	}
381
-
382
-	/**
383
-	 * see https://www.php.net/manual/en/function.hash.php
384
-	 *
385
-	 * @param string $type
386
-	 * @param string $path
387
-	 * @param bool $raw
388
-	 * @return string
389
-	 */
390
-	public function hash($type, $path, $raw = false) {
391
-		return $this->storage->hash($type, $this->findPathToUse($path), $raw);
392
-	}
393
-
394
-	/**
395
-	 * see https://www.php.net/manual/en/function.free_space.php
396
-	 *
397
-	 * @param string $path
398
-	 * @return int
399
-	 */
400
-	public function free_space($path) {
401
-		return $this->storage->free_space($this->findPathToUse($path));
402
-	}
403
-
404
-	/**
405
-	 * search for occurrences of $query in file names
406
-	 *
407
-	 * @param string $query
408
-	 * @return array
409
-	 */
410
-	public function search($query) {
411
-		return $this->storage->search($query);
412
-	}
413
-
414
-	/**
415
-	 * see https://www.php.net/manual/en/function.touch.php
416
-	 * If the backend does not support the operation, false should be returned
417
-	 *
418
-	 * @param string $path
419
-	 * @param int $mtime
420
-	 * @return bool
421
-	 */
422
-	public function touch($path, $mtime = null) {
423
-		return $this->storage->touch($this->findPathToUse($path), $mtime);
424
-	}
425
-
426
-	/**
427
-	 * get the path to a local version of the file.
428
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
429
-	 *
430
-	 * @param string $path
431
-	 * @return string
432
-	 */
433
-	public function getLocalFile($path) {
434
-		return $this->storage->getLocalFile($this->findPathToUse($path));
435
-	}
436
-
437
-	/**
438
-	 * check if a file or folder has been updated since $time
439
-	 *
440
-	 * @param string $path
441
-	 * @param int $time
442
-	 * @return bool
443
-	 *
444
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
445
-	 * returning true for other changes in the folder is optional
446
-	 */
447
-	public function hasUpdated($path, $time) {
448
-		return $this->storage->hasUpdated($this->findPathToUse($path), $time);
449
-	}
450
-
451
-	/**
452
-	 * get a cache instance for the storage
453
-	 *
454
-	 * @param string $path
455
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
456
-	 * @return \OC\Files\Cache\Cache
457
-	 */
458
-	public function getCache($path = '', $storage = null) {
459
-		if (!$storage) {
460
-			$storage = $this;
461
-		}
462
-		return $this->storage->getCache($this->findPathToUse($path), $storage);
463
-	}
464
-
465
-	/**
466
-	 * get a scanner instance for the storage
467
-	 *
468
-	 * @param string $path
469
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
470
-	 * @return \OC\Files\Cache\Scanner
471
-	 */
472
-	public function getScanner($path = '', $storage = null) {
473
-		if (!$storage) {
474
-			$storage = $this;
475
-		}
476
-		return $this->storage->getScanner($this->findPathToUse($path), $storage);
477
-	}
478
-
479
-	/**
480
-	 * get the ETag for a file or folder
481
-	 *
482
-	 * @param string $path
483
-	 * @return string
484
-	 */
485
-	public function getETag($path) {
486
-		return $this->storage->getETag($this->findPathToUse($path));
487
-	}
488
-
489
-	/**
490
-	 * @param IStorage $sourceStorage
491
-	 * @param string $sourceInternalPath
492
-	 * @param string $targetInternalPath
493
-	 * @return bool
494
-	 */
495
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
496
-		if ($sourceStorage === $this) {
497
-			return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
-		}
499
-
500
-		$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
-		if ($result) {
502
-			unset($this->namesCache[$targetInternalPath]);
503
-		}
504
-		return $result;
505
-	}
506
-
507
-	/**
508
-	 * @param IStorage $sourceStorage
509
-	 * @param string $sourceInternalPath
510
-	 * @param string $targetInternalPath
511
-	 * @return bool
512
-	 */
513
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
514
-		if ($sourceStorage === $this) {
515
-			$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
516
-			if ($result) {
517
-				unset($this->namesCache[$sourceInternalPath]);
518
-				unset($this->namesCache[$targetInternalPath]);
519
-			}
520
-			return $result;
521
-		}
522
-
523
-		$result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
524
-		if ($result) {
525
-			unset($this->namesCache[$sourceInternalPath]);
526
-			unset($this->namesCache[$targetInternalPath]);
527
-		}
528
-		return $result;
529
-	}
530
-
531
-	/**
532
-	 * @param string $path
533
-	 * @return array
534
-	 */
535
-	public function getMetaData($path) {
536
-		return $this->storage->getMetaData($this->findPathToUse($path));
537
-	}
538
-
539
-	public function getDirectoryContent($directory): \Traversable {
540
-		return $this->storage->getDirectoryContent($this->findPathToUse($directory));
541
-	}
40
+    /**
41
+     * @var ICache
42
+     */
43
+    private $namesCache;
44
+
45
+    /**
46
+     * @param array $parameters
47
+     */
48
+    public function __construct($parameters) {
49
+        $this->storage = $parameters['storage'];
50
+        $this->namesCache = new CappedMemoryCache();
51
+    }
52
+
53
+    /**
54
+     * Returns whether the given string is only made of ASCII characters
55
+     *
56
+     * @param string $str string
57
+     *
58
+     * @return bool true if the string is all ASCII, false otherwise
59
+     */
60
+    private function isAscii($str) {
61
+        return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
62
+    }
63
+
64
+    /**
65
+     * Checks whether the given path exists in NFC or NFD form after checking
66
+     * each form for each path section and returns the correct form.
67
+     * If no existing path found, returns the path as it was given.
68
+     *
69
+     * @param string $fullPath path to check
70
+     *
71
+     * @return string original or converted path
72
+     */
73
+    private function findPathToUse($fullPath) {
74
+        $cachedPath = $this->namesCache[$fullPath];
75
+        if ($cachedPath !== null) {
76
+            return $cachedPath;
77
+        }
78
+
79
+        $sections = explode('/', $fullPath);
80
+        $path = '';
81
+        foreach ($sections as $section) {
82
+            $convertedPath = $this->findPathToUseLastSection($path, $section);
83
+            if ($convertedPath === null) {
84
+                // no point in continuing if the section was not found, use original path
85
+                return $fullPath;
86
+            }
87
+            $path = $convertedPath . '/';
88
+        }
89
+        $path = rtrim($path, '/');
90
+        return $path;
91
+    }
92
+
93
+    /**
94
+     * Checks whether the last path section of the given path exists in NFC or NFD form
95
+     * and returns the correct form. If no existing path found, returns null.
96
+     *
97
+     * @param string $basePath base path to check
98
+     * @param string $lastSection last section of the path to check for NFD/NFC variations
99
+     *
100
+     * @return string|null original or converted path, or null if none of the forms was found
101
+     */
102
+    private function findPathToUseLastSection($basePath, $lastSection) {
103
+        $fullPath = $basePath . $lastSection;
104
+        if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
105
+            $this->namesCache[$fullPath] = $fullPath;
106
+            return $fullPath;
107
+        }
108
+
109
+        // swap encoding
110
+        if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
111
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
112
+        } else {
113
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
114
+        }
115
+        $otherFullPath = $basePath . $otherFormPath;
116
+        if ($this->storage->file_exists($otherFullPath)) {
117
+            $this->namesCache[$fullPath] = $otherFullPath;
118
+            return $otherFullPath;
119
+        }
120
+
121
+        // return original path, file did not exist at all
122
+        $this->namesCache[$fullPath] = $fullPath;
123
+        return null;
124
+    }
125
+
126
+    /**
127
+     * see https://www.php.net/manual/en/function.mkdir.php
128
+     *
129
+     * @param string $path
130
+     * @return bool
131
+     */
132
+    public function mkdir($path) {
133
+        // note: no conversion here, method should not be called with non-NFC names!
134
+        $result = $this->storage->mkdir($path);
135
+        if ($result) {
136
+            $this->namesCache[$path] = $path;
137
+        }
138
+        return $result;
139
+    }
140
+
141
+    /**
142
+     * see https://www.php.net/manual/en/function.rmdir.php
143
+     *
144
+     * @param string $path
145
+     * @return bool
146
+     */
147
+    public function rmdir($path) {
148
+        $result = $this->storage->rmdir($this->findPathToUse($path));
149
+        if ($result) {
150
+            unset($this->namesCache[$path]);
151
+        }
152
+        return $result;
153
+    }
154
+
155
+    /**
156
+     * see https://www.php.net/manual/en/function.opendir.php
157
+     *
158
+     * @param string $path
159
+     * @return resource
160
+     */
161
+    public function opendir($path) {
162
+        return $this->storage->opendir($this->findPathToUse($path));
163
+    }
164
+
165
+    /**
166
+     * see https://www.php.net/manual/en/function.is_dir.php
167
+     *
168
+     * @param string $path
169
+     * @return bool
170
+     */
171
+    public function is_dir($path) {
172
+        return $this->storage->is_dir($this->findPathToUse($path));
173
+    }
174
+
175
+    /**
176
+     * see https://www.php.net/manual/en/function.is_file.php
177
+     *
178
+     * @param string $path
179
+     * @return bool
180
+     */
181
+    public function is_file($path) {
182
+        return $this->storage->is_file($this->findPathToUse($path));
183
+    }
184
+
185
+    /**
186
+     * see https://www.php.net/manual/en/function.stat.php
187
+     * only the following keys are required in the result: size and mtime
188
+     *
189
+     * @param string $path
190
+     * @return array
191
+     */
192
+    public function stat($path) {
193
+        return $this->storage->stat($this->findPathToUse($path));
194
+    }
195
+
196
+    /**
197
+     * see https://www.php.net/manual/en/function.filetype.php
198
+     *
199
+     * @param string $path
200
+     * @return bool
201
+     */
202
+    public function filetype($path) {
203
+        return $this->storage->filetype($this->findPathToUse($path));
204
+    }
205
+
206
+    /**
207
+     * see https://www.php.net/manual/en/function.filesize.php
208
+     * The result for filesize when called on a folder is required to be 0
209
+     *
210
+     * @param string $path
211
+     * @return int
212
+     */
213
+    public function filesize($path) {
214
+        return $this->storage->filesize($this->findPathToUse($path));
215
+    }
216
+
217
+    /**
218
+     * check if a file can be created in $path
219
+     *
220
+     * @param string $path
221
+     * @return bool
222
+     */
223
+    public function isCreatable($path) {
224
+        return $this->storage->isCreatable($this->findPathToUse($path));
225
+    }
226
+
227
+    /**
228
+     * check if a file can be read
229
+     *
230
+     * @param string $path
231
+     * @return bool
232
+     */
233
+    public function isReadable($path) {
234
+        return $this->storage->isReadable($this->findPathToUse($path));
235
+    }
236
+
237
+    /**
238
+     * check if a file can be written to
239
+     *
240
+     * @param string $path
241
+     * @return bool
242
+     */
243
+    public function isUpdatable($path) {
244
+        return $this->storage->isUpdatable($this->findPathToUse($path));
245
+    }
246
+
247
+    /**
248
+     * check if a file can be deleted
249
+     *
250
+     * @param string $path
251
+     * @return bool
252
+     */
253
+    public function isDeletable($path) {
254
+        return $this->storage->isDeletable($this->findPathToUse($path));
255
+    }
256
+
257
+    /**
258
+     * check if a file can be shared
259
+     *
260
+     * @param string $path
261
+     * @return bool
262
+     */
263
+    public function isSharable($path) {
264
+        return $this->storage->isSharable($this->findPathToUse($path));
265
+    }
266
+
267
+    /**
268
+     * get the full permissions of a path.
269
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
270
+     *
271
+     * @param string $path
272
+     * @return int
273
+     */
274
+    public function getPermissions($path) {
275
+        return $this->storage->getPermissions($this->findPathToUse($path));
276
+    }
277
+
278
+    /**
279
+     * see https://www.php.net/manual/en/function.file_exists.php
280
+     *
281
+     * @param string $path
282
+     * @return bool
283
+     */
284
+    public function file_exists($path) {
285
+        return $this->storage->file_exists($this->findPathToUse($path));
286
+    }
287
+
288
+    /**
289
+     * see https://www.php.net/manual/en/function.filemtime.php
290
+     *
291
+     * @param string $path
292
+     * @return int
293
+     */
294
+    public function filemtime($path) {
295
+        return $this->storage->filemtime($this->findPathToUse($path));
296
+    }
297
+
298
+    /**
299
+     * see https://www.php.net/manual/en/function.file_get_contents.php
300
+     *
301
+     * @param string $path
302
+     * @return string
303
+     */
304
+    public function file_get_contents($path) {
305
+        return $this->storage->file_get_contents($this->findPathToUse($path));
306
+    }
307
+
308
+    /**
309
+     * see https://www.php.net/manual/en/function.file_put_contents.php
310
+     *
311
+     * @param string $path
312
+     * @param mixed $data
313
+     * @return int|false
314
+     */
315
+    public function file_put_contents($path, $data) {
316
+        return $this->storage->file_put_contents($this->findPathToUse($path), $data);
317
+    }
318
+
319
+    /**
320
+     * see https://www.php.net/manual/en/function.unlink.php
321
+     *
322
+     * @param string $path
323
+     * @return bool
324
+     */
325
+    public function unlink($path) {
326
+        $result = $this->storage->unlink($this->findPathToUse($path));
327
+        if ($result) {
328
+            unset($this->namesCache[$path]);
329
+        }
330
+        return $result;
331
+    }
332
+
333
+    /**
334
+     * see https://www.php.net/manual/en/function.rename.php
335
+     *
336
+     * @param string $path1
337
+     * @param string $path2
338
+     * @return bool
339
+     */
340
+    public function rename($path1, $path2) {
341
+        // second name always NFC
342
+        return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
343
+    }
344
+
345
+    /**
346
+     * see https://www.php.net/manual/en/function.copy.php
347
+     *
348
+     * @param string $path1
349
+     * @param string $path2
350
+     * @return bool
351
+     */
352
+    public function copy($path1, $path2) {
353
+        return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
354
+    }
355
+
356
+    /**
357
+     * see https://www.php.net/manual/en/function.fopen.php
358
+     *
359
+     * @param string $path
360
+     * @param string $mode
361
+     * @return resource
362
+     */
363
+    public function fopen($path, $mode) {
364
+        $result = $this->storage->fopen($this->findPathToUse($path), $mode);
365
+        if ($result && $mode !== 'r' && $mode !== 'rb') {
366
+            unset($this->namesCache[$path]);
367
+        }
368
+        return $result;
369
+    }
370
+
371
+    /**
372
+     * get the mimetype for a file or folder
373
+     * The mimetype for a folder is required to be "httpd/unix-directory"
374
+     *
375
+     * @param string $path
376
+     * @return string
377
+     */
378
+    public function getMimeType($path) {
379
+        return $this->storage->getMimeType($this->findPathToUse($path));
380
+    }
381
+
382
+    /**
383
+     * see https://www.php.net/manual/en/function.hash.php
384
+     *
385
+     * @param string $type
386
+     * @param string $path
387
+     * @param bool $raw
388
+     * @return string
389
+     */
390
+    public function hash($type, $path, $raw = false) {
391
+        return $this->storage->hash($type, $this->findPathToUse($path), $raw);
392
+    }
393
+
394
+    /**
395
+     * see https://www.php.net/manual/en/function.free_space.php
396
+     *
397
+     * @param string $path
398
+     * @return int
399
+     */
400
+    public function free_space($path) {
401
+        return $this->storage->free_space($this->findPathToUse($path));
402
+    }
403
+
404
+    /**
405
+     * search for occurrences of $query in file names
406
+     *
407
+     * @param string $query
408
+     * @return array
409
+     */
410
+    public function search($query) {
411
+        return $this->storage->search($query);
412
+    }
413
+
414
+    /**
415
+     * see https://www.php.net/manual/en/function.touch.php
416
+     * If the backend does not support the operation, false should be returned
417
+     *
418
+     * @param string $path
419
+     * @param int $mtime
420
+     * @return bool
421
+     */
422
+    public function touch($path, $mtime = null) {
423
+        return $this->storage->touch($this->findPathToUse($path), $mtime);
424
+    }
425
+
426
+    /**
427
+     * get the path to a local version of the file.
428
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
429
+     *
430
+     * @param string $path
431
+     * @return string
432
+     */
433
+    public function getLocalFile($path) {
434
+        return $this->storage->getLocalFile($this->findPathToUse($path));
435
+    }
436
+
437
+    /**
438
+     * check if a file or folder has been updated since $time
439
+     *
440
+     * @param string $path
441
+     * @param int $time
442
+     * @return bool
443
+     *
444
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
445
+     * returning true for other changes in the folder is optional
446
+     */
447
+    public function hasUpdated($path, $time) {
448
+        return $this->storage->hasUpdated($this->findPathToUse($path), $time);
449
+    }
450
+
451
+    /**
452
+     * get a cache instance for the storage
453
+     *
454
+     * @param string $path
455
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
456
+     * @return \OC\Files\Cache\Cache
457
+     */
458
+    public function getCache($path = '', $storage = null) {
459
+        if (!$storage) {
460
+            $storage = $this;
461
+        }
462
+        return $this->storage->getCache($this->findPathToUse($path), $storage);
463
+    }
464
+
465
+    /**
466
+     * get a scanner instance for the storage
467
+     *
468
+     * @param string $path
469
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
470
+     * @return \OC\Files\Cache\Scanner
471
+     */
472
+    public function getScanner($path = '', $storage = null) {
473
+        if (!$storage) {
474
+            $storage = $this;
475
+        }
476
+        return $this->storage->getScanner($this->findPathToUse($path), $storage);
477
+    }
478
+
479
+    /**
480
+     * get the ETag for a file or folder
481
+     *
482
+     * @param string $path
483
+     * @return string
484
+     */
485
+    public function getETag($path) {
486
+        return $this->storage->getETag($this->findPathToUse($path));
487
+    }
488
+
489
+    /**
490
+     * @param IStorage $sourceStorage
491
+     * @param string $sourceInternalPath
492
+     * @param string $targetInternalPath
493
+     * @return bool
494
+     */
495
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
496
+        if ($sourceStorage === $this) {
497
+            return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
+        }
499
+
500
+        $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
501
+        if ($result) {
502
+            unset($this->namesCache[$targetInternalPath]);
503
+        }
504
+        return $result;
505
+    }
506
+
507
+    /**
508
+     * @param IStorage $sourceStorage
509
+     * @param string $sourceInternalPath
510
+     * @param string $targetInternalPath
511
+     * @return bool
512
+     */
513
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
514
+        if ($sourceStorage === $this) {
515
+            $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
516
+            if ($result) {
517
+                unset($this->namesCache[$sourceInternalPath]);
518
+                unset($this->namesCache[$targetInternalPath]);
519
+            }
520
+            return $result;
521
+        }
522
+
523
+        $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
524
+        if ($result) {
525
+            unset($this->namesCache[$sourceInternalPath]);
526
+            unset($this->namesCache[$targetInternalPath]);
527
+        }
528
+        return $result;
529
+    }
530
+
531
+    /**
532
+     * @param string $path
533
+     * @return array
534
+     */
535
+    public function getMetaData($path) {
536
+        return $this->storage->getMetaData($this->findPathToUse($path));
537
+    }
538
+
539
+    public function getDirectoryContent($directory): \Traversable {
540
+        return $this->storage->getDirectoryContent($this->findPathToUse($directory));
541
+    }
542 542
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Quota.php 1 patch
Indentation   +204 added lines, -204 removed lines patch added patch discarded remove patch
@@ -36,208 +36,208 @@
 block discarded – undo
36 36
 
37 37
 class Quota extends Wrapper {
38 38
 
39
-	/**
40
-	 * @var int $quota
41
-	 */
42
-	protected $quota;
43
-
44
-	/**
45
-	 * @var string $sizeRoot
46
-	 */
47
-	protected $sizeRoot;
48
-
49
-	private $config;
50
-
51
-	/**
52
-	 * @param array $parameters
53
-	 */
54
-	public function __construct($parameters) {
55
-		parent::__construct($parameters);
56
-		$this->quota = $parameters['quota'];
57
-		$this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : '';
58
-		$this->config = \OC::$server->getSystemConfig();
59
-	}
60
-
61
-	/**
62
-	 * @return int quota value
63
-	 */
64
-	public function getQuota() {
65
-		return $this->quota;
66
-	}
67
-
68
-	/**
69
-	 * @param string $path
70
-	 * @param \OC\Files\Storage\Storage $storage
71
-	 */
72
-	protected function getSize($path, $storage = null) {
73
-		if ($this->config->getValue('quota_include_external_storage', false)) {
74
-			$rootInfo = Filesystem::getFileInfo('', 'ext');
75
-			if ($rootInfo) {
76
-				return $rootInfo->getSize(true);
77
-			}
78
-			return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
79
-		} else {
80
-			if (is_null($storage)) {
81
-				$cache = $this->getCache();
82
-			} else {
83
-				$cache = $storage->getCache();
84
-			}
85
-			$data = $cache->get($path);
86
-			if ($data instanceof ICacheEntry and isset($data['size'])) {
87
-				return $data['size'];
88
-			} else {
89
-				return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
90
-			}
91
-		}
92
-	}
93
-
94
-	/**
95
-	 * Get free space as limited by the quota
96
-	 *
97
-	 * @param string $path
98
-	 * @return int
99
-	 */
100
-	public function free_space($path) {
101
-		if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) {
102
-			return $this->storage->free_space($path);
103
-		} else {
104
-			$used = $this->getSize($this->sizeRoot);
105
-			if ($used < 0) {
106
-				return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
107
-			} else {
108
-				$free = $this->storage->free_space($path);
109
-				$quotaFree = max($this->quota - $used, 0);
110
-				// if free space is known
111
-				if ($free >= 0) {
112
-					$free = min($free, $quotaFree);
113
-				} else {
114
-					$free = $quotaFree;
115
-				}
116
-				return $free;
117
-			}
118
-		}
119
-	}
120
-
121
-	/**
122
-	 * see https://www.php.net/manual/en/function.file_put_contents.php
123
-	 *
124
-	 * @param string $path
125
-	 * @param mixed $data
126
-	 * @return int|false
127
-	 */
128
-	public function file_put_contents($path, $data) {
129
-		$free = $this->free_space($path);
130
-		if ($free < 0 or strlen($data) < $free) {
131
-			return $this->storage->file_put_contents($path, $data);
132
-		} else {
133
-			return false;
134
-		}
135
-	}
136
-
137
-	/**
138
-	 * see https://www.php.net/manual/en/function.copy.php
139
-	 *
140
-	 * @param string $source
141
-	 * @param string $target
142
-	 * @return bool
143
-	 */
144
-	public function copy($source, $target) {
145
-		$free = $this->free_space($target);
146
-		if ($free < 0 or $this->getSize($source) < $free) {
147
-			return $this->storage->copy($source, $target);
148
-		} else {
149
-			return false;
150
-		}
151
-	}
152
-
153
-	/**
154
-	 * see https://www.php.net/manual/en/function.fopen.php
155
-	 *
156
-	 * @param string $path
157
-	 * @param string $mode
158
-	 * @return resource
159
-	 */
160
-	public function fopen($path, $mode) {
161
-		$source = $this->storage->fopen($path, $mode);
162
-
163
-		// don't apply quota for part files
164
-		if (!$this->isPartFile($path)) {
165
-			$free = $this->free_space($path);
166
-			if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
167
-				// only apply quota for files, not metadata, trash or others
168
-				if ($this->shouldApplyQuota($path)) {
169
-					return \OC\Files\Stream\Quota::wrap($source, $free);
170
-				}
171
-			}
172
-		}
173
-		return $source;
174
-	}
175
-
176
-	/**
177
-	 * Checks whether the given path is a part file
178
-	 *
179
-	 * @param string $path Path that may identify a .part file
180
-	 * @return string File path without .part extension
181
-	 * @note this is needed for reusing keys
182
-	 */
183
-	private function isPartFile($path) {
184
-		$extension = pathinfo($path, PATHINFO_EXTENSION);
185
-
186
-		return ($extension === 'part');
187
-	}
188
-
189
-	/**
190
-	 * Only apply quota for files, not metadata, trash or others
191
-	 */
192
-	private function shouldApplyQuota(string $path): bool {
193
-		return strpos(ltrim($path, '/'), 'files/') === 0;
194
-	}
195
-
196
-	/**
197
-	 * @param IStorage $sourceStorage
198
-	 * @param string $sourceInternalPath
199
-	 * @param string $targetInternalPath
200
-	 * @return bool
201
-	 */
202
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
203
-		$free = $this->free_space($targetInternalPath);
204
-		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
205
-			return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
206
-		} else {
207
-			return false;
208
-		}
209
-	}
210
-
211
-	/**
212
-	 * @param IStorage $sourceStorage
213
-	 * @param string $sourceInternalPath
214
-	 * @param string $targetInternalPath
215
-	 * @return bool
216
-	 */
217
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
218
-		$free = $this->free_space($targetInternalPath);
219
-		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
220
-			return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
221
-		} else {
222
-			return false;
223
-		}
224
-	}
225
-
226
-	public function mkdir($path) {
227
-		$free = $this->free_space($path);
228
-		if ($this->shouldApplyQuota($path) && $free === 0.0) {
229
-			return false;
230
-		}
231
-
232
-		return parent::mkdir($path);
233
-	}
234
-
235
-	public function touch($path, $mtime = null) {
236
-		$free = $this->free_space($path);
237
-		if ($free === 0.0) {
238
-			return false;
239
-		}
240
-
241
-		return parent::touch($path, $mtime);
242
-	}
39
+    /**
40
+     * @var int $quota
41
+     */
42
+    protected $quota;
43
+
44
+    /**
45
+     * @var string $sizeRoot
46
+     */
47
+    protected $sizeRoot;
48
+
49
+    private $config;
50
+
51
+    /**
52
+     * @param array $parameters
53
+     */
54
+    public function __construct($parameters) {
55
+        parent::__construct($parameters);
56
+        $this->quota = $parameters['quota'];
57
+        $this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : '';
58
+        $this->config = \OC::$server->getSystemConfig();
59
+    }
60
+
61
+    /**
62
+     * @return int quota value
63
+     */
64
+    public function getQuota() {
65
+        return $this->quota;
66
+    }
67
+
68
+    /**
69
+     * @param string $path
70
+     * @param \OC\Files\Storage\Storage $storage
71
+     */
72
+    protected function getSize($path, $storage = null) {
73
+        if ($this->config->getValue('quota_include_external_storage', false)) {
74
+            $rootInfo = Filesystem::getFileInfo('', 'ext');
75
+            if ($rootInfo) {
76
+                return $rootInfo->getSize(true);
77
+            }
78
+            return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
79
+        } else {
80
+            if (is_null($storage)) {
81
+                $cache = $this->getCache();
82
+            } else {
83
+                $cache = $storage->getCache();
84
+            }
85
+            $data = $cache->get($path);
86
+            if ($data instanceof ICacheEntry and isset($data['size'])) {
87
+                return $data['size'];
88
+            } else {
89
+                return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
90
+            }
91
+        }
92
+    }
93
+
94
+    /**
95
+     * Get free space as limited by the quota
96
+     *
97
+     * @param string $path
98
+     * @return int
99
+     */
100
+    public function free_space($path) {
101
+        if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) {
102
+            return $this->storage->free_space($path);
103
+        } else {
104
+            $used = $this->getSize($this->sizeRoot);
105
+            if ($used < 0) {
106
+                return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
107
+            } else {
108
+                $free = $this->storage->free_space($path);
109
+                $quotaFree = max($this->quota - $used, 0);
110
+                // if free space is known
111
+                if ($free >= 0) {
112
+                    $free = min($free, $quotaFree);
113
+                } else {
114
+                    $free = $quotaFree;
115
+                }
116
+                return $free;
117
+            }
118
+        }
119
+    }
120
+
121
+    /**
122
+     * see https://www.php.net/manual/en/function.file_put_contents.php
123
+     *
124
+     * @param string $path
125
+     * @param mixed $data
126
+     * @return int|false
127
+     */
128
+    public function file_put_contents($path, $data) {
129
+        $free = $this->free_space($path);
130
+        if ($free < 0 or strlen($data) < $free) {
131
+            return $this->storage->file_put_contents($path, $data);
132
+        } else {
133
+            return false;
134
+        }
135
+    }
136
+
137
+    /**
138
+     * see https://www.php.net/manual/en/function.copy.php
139
+     *
140
+     * @param string $source
141
+     * @param string $target
142
+     * @return bool
143
+     */
144
+    public function copy($source, $target) {
145
+        $free = $this->free_space($target);
146
+        if ($free < 0 or $this->getSize($source) < $free) {
147
+            return $this->storage->copy($source, $target);
148
+        } else {
149
+            return false;
150
+        }
151
+    }
152
+
153
+    /**
154
+     * see https://www.php.net/manual/en/function.fopen.php
155
+     *
156
+     * @param string $path
157
+     * @param string $mode
158
+     * @return resource
159
+     */
160
+    public function fopen($path, $mode) {
161
+        $source = $this->storage->fopen($path, $mode);
162
+
163
+        // don't apply quota for part files
164
+        if (!$this->isPartFile($path)) {
165
+            $free = $this->free_space($path);
166
+            if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
167
+                // only apply quota for files, not metadata, trash or others
168
+                if ($this->shouldApplyQuota($path)) {
169
+                    return \OC\Files\Stream\Quota::wrap($source, $free);
170
+                }
171
+            }
172
+        }
173
+        return $source;
174
+    }
175
+
176
+    /**
177
+     * Checks whether the given path is a part file
178
+     *
179
+     * @param string $path Path that may identify a .part file
180
+     * @return string File path without .part extension
181
+     * @note this is needed for reusing keys
182
+     */
183
+    private function isPartFile($path) {
184
+        $extension = pathinfo($path, PATHINFO_EXTENSION);
185
+
186
+        return ($extension === 'part');
187
+    }
188
+
189
+    /**
190
+     * Only apply quota for files, not metadata, trash or others
191
+     */
192
+    private function shouldApplyQuota(string $path): bool {
193
+        return strpos(ltrim($path, '/'), 'files/') === 0;
194
+    }
195
+
196
+    /**
197
+     * @param IStorage $sourceStorage
198
+     * @param string $sourceInternalPath
199
+     * @param string $targetInternalPath
200
+     * @return bool
201
+     */
202
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
203
+        $free = $this->free_space($targetInternalPath);
204
+        if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
205
+            return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
206
+        } else {
207
+            return false;
208
+        }
209
+    }
210
+
211
+    /**
212
+     * @param IStorage $sourceStorage
213
+     * @param string $sourceInternalPath
214
+     * @param string $targetInternalPath
215
+     * @return bool
216
+     */
217
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
218
+        $free = $this->free_space($targetInternalPath);
219
+        if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
220
+            return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
221
+        } else {
222
+            return false;
223
+        }
224
+    }
225
+
226
+    public function mkdir($path) {
227
+        $free = $this->free_space($path);
228
+        if ($this->shouldApplyQuota($path) && $free === 0.0) {
229
+            return false;
230
+        }
231
+
232
+        return parent::mkdir($path);
233
+    }
234
+
235
+    public function touch($path, $mtime = null) {
236
+        $free = $this->free_space($path);
237
+        if ($free === 0.0) {
238
+            return false;
239
+        }
240
+
241
+        return parent::touch($path, $mtime);
242
+    }
243 243
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_Image.php 1 patch
Indentation   +1224 added lines, -1224 removed lines patch added patch discarded remove patch
@@ -45,569 +45,569 @@  discard block
 block discarded – undo
45 45
  * Class for basic image manipulation
46 46
  */
47 47
 class OC_Image implements \OCP\IImage {
48
-	/** @var false|resource */
49
-	protected $resource = false; // tmp resource.
50
-	/** @var int */
51
-	protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
52
-	/** @var string */
53
-	protected $mimeType = 'image/png'; // Default to png
54
-	/** @var int */
55
-	protected $bitDepth = 24;
56
-	/** @var null|string */
57
-	protected $filePath = null;
58
-	/** @var finfo */
59
-	private $fileInfo;
60
-	/** @var \OCP\ILogger */
61
-	private $logger;
62
-	/** @var \OCP\IConfig */
63
-	private $config;
64
-	/** @var array */
65
-	private $exif;
66
-
67
-	/**
68
-	 * Constructor.
69
-	 *
70
-	 * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by
71
-	 * an imagecreate* function.
72
-	 * @param \OCP\ILogger $logger
73
-	 * @param \OCP\IConfig $config
74
-	 * @throws \InvalidArgumentException in case the $imageRef parameter is not null
75
-	 */
76
-	public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) {
77
-		$this->logger = $logger;
78
-		if ($logger === null) {
79
-			$this->logger = \OC::$server->getLogger();
80
-		}
81
-		$this->config = $config;
82
-		if ($config === null) {
83
-			$this->config = \OC::$server->getConfig();
84
-		}
85
-
86
-		if (\OC_Util::fileInfoLoaded()) {
87
-			$this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
88
-		}
89
-
90
-		if ($imageRef !== null) {
91
-			throw new \InvalidArgumentException('The first parameter in the constructor is not supported anymore. Please use any of the load* methods of the image object to load an image.');
92
-		}
93
-	}
94
-
95
-	/**
96
-	 * Determine whether the object contains an image resource.
97
-	 *
98
-	 * @return bool
99
-	 */
100
-	public function valid() { // apparently you can't name a method 'empty'...
101
-		if (is_resource($this->resource)) {
102
-			return true;
103
-		}
104
-		if (is_object($this->resource) && get_class($this->resource) === 'GdImage') {
105
-			return true;
106
-		}
107
-
108
-		return false;
109
-	}
110
-
111
-	/**
112
-	 * Returns the MIME type of the image or an empty string if no image is loaded.
113
-	 *
114
-	 * @return string
115
-	 */
116
-	public function mimeType() {
117
-		return $this->valid() ? $this->mimeType : '';
118
-	}
119
-
120
-	/**
121
-	 * Returns the width of the image or -1 if no image is loaded.
122
-	 *
123
-	 * @return int
124
-	 */
125
-	public function width() {
126
-		return $this->valid() ? imagesx($this->resource) : -1;
127
-	}
128
-
129
-	/**
130
-	 * Returns the height of the image or -1 if no image is loaded.
131
-	 *
132
-	 * @return int
133
-	 */
134
-	public function height() {
135
-		return $this->valid() ? imagesy($this->resource) : -1;
136
-	}
137
-
138
-	/**
139
-	 * Returns the width when the image orientation is top-left.
140
-	 *
141
-	 * @return int
142
-	 */
143
-	public function widthTopLeft() {
144
-		$o = $this->getOrientation();
145
-		$this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, ['app' => 'core']);
146
-		switch ($o) {
147
-			case -1:
148
-			case 1:
149
-			case 2: // Not tested
150
-			case 3:
151
-			case 4: // Not tested
152
-				return $this->width();
153
-			case 5: // Not tested
154
-			case 6:
155
-			case 7: // Not tested
156
-			case 8:
157
-				return $this->height();
158
-		}
159
-		return $this->width();
160
-	}
161
-
162
-	/**
163
-	 * Returns the height when the image orientation is top-left.
164
-	 *
165
-	 * @return int
166
-	 */
167
-	public function heightTopLeft() {
168
-		$o = $this->getOrientation();
169
-		$this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, ['app' => 'core']);
170
-		switch ($o) {
171
-			case -1:
172
-			case 1:
173
-			case 2: // Not tested
174
-			case 3:
175
-			case 4: // Not tested
176
-				return $this->height();
177
-			case 5: // Not tested
178
-			case 6:
179
-			case 7: // Not tested
180
-			case 8:
181
-				return $this->width();
182
-		}
183
-		return $this->height();
184
-	}
185
-
186
-	/**
187
-	 * Outputs the image.
188
-	 *
189
-	 * @param string $mimeType
190
-	 * @return bool
191
-	 */
192
-	public function show($mimeType = null) {
193
-		if ($mimeType === null) {
194
-			$mimeType = $this->mimeType();
195
-		}
196
-		header('Content-Type: ' . $mimeType);
197
-		return $this->_output(null, $mimeType);
198
-	}
199
-
200
-	/**
201
-	 * Saves the image.
202
-	 *
203
-	 * @param string $filePath
204
-	 * @param string $mimeType
205
-	 * @return bool
206
-	 */
207
-
208
-	public function save($filePath = null, $mimeType = null) {
209
-		if ($mimeType === null) {
210
-			$mimeType = $this->mimeType();
211
-		}
212
-		if ($filePath === null) {
213
-			if ($this->filePath === null) {
214
-				$this->logger->error(__METHOD__ . '(): called with no path.', ['app' => 'core']);
215
-				return false;
216
-			} else {
217
-				$filePath = $this->filePath;
218
-			}
219
-		}
220
-		return $this->_output($filePath, $mimeType);
221
-	}
222
-
223
-	/**
224
-	 * Outputs/saves the image.
225
-	 *
226
-	 * @param string $filePath
227
-	 * @param string $mimeType
228
-	 * @return bool
229
-	 * @throws Exception
230
-	 */
231
-	private function _output($filePath = null, $mimeType = null) {
232
-		if ($filePath) {
233
-			if (!file_exists(dirname($filePath))) {
234
-				mkdir(dirname($filePath), 0777, true);
235
-			}
236
-			$isWritable = is_writable(dirname($filePath));
237
-			if (!$isWritable) {
238
-				$this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', ['app' => 'core']);
239
-				return false;
240
-			} elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
241
-				$this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']);
242
-				return false;
243
-			}
244
-		}
245
-		if (!$this->valid()) {
246
-			return false;
247
-		}
248
-
249
-		$imageType = $this->imageType;
250
-		if ($mimeType !== null) {
251
-			switch ($mimeType) {
252
-				case 'image/gif':
253
-					$imageType = IMAGETYPE_GIF;
254
-					break;
255
-				case 'image/jpeg':
256
-					$imageType = IMAGETYPE_JPEG;
257
-					break;
258
-				case 'image/png':
259
-					$imageType = IMAGETYPE_PNG;
260
-					break;
261
-				case 'image/x-xbitmap':
262
-					$imageType = IMAGETYPE_XBM;
263
-					break;
264
-				case 'image/bmp':
265
-				case 'image/x-ms-bmp':
266
-					$imageType = IMAGETYPE_BMP;
267
-					break;
268
-				default:
269
-					throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
270
-			}
271
-		}
272
-
273
-		switch ($imageType) {
274
-			case IMAGETYPE_GIF:
275
-				$retVal = imagegif($this->resource, $filePath);
276
-				break;
277
-			case IMAGETYPE_JPEG:
278
-				$retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
279
-				break;
280
-			case IMAGETYPE_PNG:
281
-				$retVal = imagepng($this->resource, $filePath);
282
-				break;
283
-			case IMAGETYPE_XBM:
284
-				if (function_exists('imagexbm')) {
285
-					$retVal = imagexbm($this->resource, $filePath);
286
-				} else {
287
-					throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
288
-				}
289
-
290
-				break;
291
-			case IMAGETYPE_WBMP:
292
-				$retVal = imagewbmp($this->resource, $filePath);
293
-				break;
294
-			case IMAGETYPE_BMP:
295
-				$retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
296
-				break;
297
-			default:
298
-				$retVal = imagepng($this->resource, $filePath);
299
-		}
300
-		return $retVal;
301
-	}
302
-
303
-	/**
304
-	 * Prints the image when called as $image().
305
-	 */
306
-	public function __invoke() {
307
-		return $this->show();
308
-	}
309
-
310
-	/**
311
-	 * @param resource Returns the image resource in any.
312
-	 * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd"
313
-	 */
314
-	public function setResource($resource) {
315
-		// For PHP<8
316
-		if (is_resource($resource) && get_resource_type($resource) === 'gd') {
317
-			$this->resource = $resource;
318
-			return;
319
-		}
320
-		// PHP 8 has real objects for GD stuff
321
-		if (is_object($resource) && get_class($resource) === 'GdImage') {
322
-			$this->resource = $resource;
323
-			return;
324
-		}
325
-		throw new \InvalidArgumentException('Supplied resource is not of type "gd".');
326
-	}
327
-
328
-	/**
329
-	 * @return resource Returns the image resource in any.
330
-	 */
331
-	public function resource() {
332
-		return $this->resource;
333
-	}
334
-
335
-	/**
336
-	 * @return string Returns the mimetype of the data. Returns the empty string
337
-	 * if the data is not valid.
338
-	 */
339
-	public function dataMimeType() {
340
-		if (!$this->valid()) {
341
-			return '';
342
-		}
343
-
344
-		switch ($this->mimeType) {
345
-			case 'image/png':
346
-			case 'image/jpeg':
347
-			case 'image/gif':
348
-				return $this->mimeType;
349
-			default:
350
-				return 'image/png';
351
-		}
352
-	}
353
-
354
-	/**
355
-	 * @return null|string Returns the raw image data.
356
-	 */
357
-	public function data() {
358
-		if (!$this->valid()) {
359
-			return null;
360
-		}
361
-		ob_start();
362
-		switch ($this->mimeType) {
363
-			case "image/png":
364
-				$res = imagepng($this->resource);
365
-				break;
366
-			case "image/jpeg":
367
-				$quality = $this->getJpegQuality();
368
-				if ($quality !== null) {
369
-					$res = imagejpeg($this->resource, null, $quality);
370
-				} else {
371
-					$res = imagejpeg($this->resource);
372
-				}
373
-				break;
374
-			case "image/gif":
375
-				$res = imagegif($this->resource);
376
-				break;
377
-			default:
378
-				$res = imagepng($this->resource);
379
-				$this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', ['app' => 'core']);
380
-				break;
381
-		}
382
-		if (!$res) {
383
-			$this->logger->error('OC_Image->data. Error getting image data.', ['app' => 'core']);
384
-		}
385
-		return ob_get_clean();
386
-	}
387
-
388
-	/**
389
-	 * @return string - base64 encoded, which is suitable for embedding in a VCard.
390
-	 */
391
-	public function __toString() {
392
-		return base64_encode($this->data());
393
-	}
394
-
395
-	/**
396
-	 * @return int|null
397
-	 */
398
-	protected function getJpegQuality() {
399
-		$quality = $this->config->getAppValue('preview', 'jpeg_quality', 90);
400
-		if ($quality !== null) {
401
-			$quality = min(100, max(10, (int) $quality));
402
-		}
403
-		return $quality;
404
-	}
405
-
406
-	/**
407
-	 * (I'm open for suggestions on better method name ;)
408
-	 * Get the orientation based on EXIF data.
409
-	 *
410
-	 * @return int The orientation or -1 if no EXIF data is available.
411
-	 */
412
-	public function getOrientation() {
413
-		if ($this->exif !== null) {
414
-			return $this->exif['Orientation'];
415
-		}
416
-
417
-		if ($this->imageType !== IMAGETYPE_JPEG) {
418
-			$this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', ['app' => 'core']);
419
-			return -1;
420
-		}
421
-		if (!is_callable('exif_read_data')) {
422
-			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
423
-			return -1;
424
-		}
425
-		if (!$this->valid()) {
426
-			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
427
-			return -1;
428
-		}
429
-		if (is_null($this->filePath) || !is_readable($this->filePath)) {
430
-			$this->logger->debug('OC_Image->fixOrientation() No readable file path set.', ['app' => 'core']);
431
-			return -1;
432
-		}
433
-		$exif = @exif_read_data($this->filePath, 'IFD0');
434
-		if (!$exif) {
435
-			return -1;
436
-		}
437
-		if (!isset($exif['Orientation'])) {
438
-			return -1;
439
-		}
440
-		$this->exif = $exif;
441
-		return $exif['Orientation'];
442
-	}
443
-
444
-	public function readExif($data) {
445
-		if (!is_callable('exif_read_data')) {
446
-			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
447
-			return;
448
-		}
449
-		if (!$this->valid()) {
450
-			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
451
-			return;
452
-		}
453
-
454
-		$exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
455
-		if (!$exif) {
456
-			return;
457
-		}
458
-		if (!isset($exif['Orientation'])) {
459
-			return;
460
-		}
461
-		$this->exif = $exif;
462
-	}
463
-
464
-	/**
465
-	 * (I'm open for suggestions on better method name ;)
466
-	 * Fixes orientation based on EXIF data.
467
-	 *
468
-	 * @return bool
469
-	 */
470
-	public function fixOrientation() {
471
-		$o = $this->getOrientation();
472
-		$this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, ['app' => 'core']);
473
-		$rotate = 0;
474
-		$flip = false;
475
-		switch ($o) {
476
-			case -1:
477
-				return false; //Nothing to fix
478
-			case 1:
479
-				$rotate = 0;
480
-				break;
481
-			case 2:
482
-				$rotate = 0;
483
-				$flip = true;
484
-				break;
485
-			case 3:
486
-				$rotate = 180;
487
-				break;
488
-			case 4:
489
-				$rotate = 180;
490
-				$flip = true;
491
-				break;
492
-			case 5:
493
-				$rotate = 90;
494
-				$flip = true;
495
-				break;
496
-			case 6:
497
-				$rotate = 270;
498
-				break;
499
-			case 7:
500
-				$rotate = 270;
501
-				$flip = true;
502
-				break;
503
-			case 8:
504
-				$rotate = 90;
505
-				break;
506
-		}
507
-		if ($flip && function_exists('imageflip')) {
508
-			imageflip($this->resource, IMG_FLIP_HORIZONTAL);
509
-		}
510
-		if ($rotate) {
511
-			$res = imagerotate($this->resource, $rotate, 0);
512
-			if ($res) {
513
-				if (imagealphablending($res, true)) {
514
-					if (imagesavealpha($res, true)) {
515
-						imagedestroy($this->resource);
516
-						$this->resource = $res;
517
-						return true;
518
-					} else {
519
-						$this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']);
520
-						return false;
521
-					}
522
-				} else {
523
-					$this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']);
524
-					return false;
525
-				}
526
-			} else {
527
-				$this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']);
528
-				return false;
529
-			}
530
-		}
531
-		return false;
532
-	}
533
-
534
-	/**
535
-	 * Loads an image from an open file handle.
536
-	 * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
537
-	 *
538
-	 * @param resource $handle
539
-	 * @return resource|false An image resource or false on error
540
-	 */
541
-	public function loadFromFileHandle($handle) {
542
-		$contents = stream_get_contents($handle);
543
-		if ($this->loadFromData($contents)) {
544
-			return $this->resource;
545
-		}
546
-		return false;
547
-	}
548
-
549
-	/**
550
-	 * Loads an image from a local file.
551
-	 *
552
-	 * @param bool|string $imagePath The path to a local file.
553
-	 * @return bool|resource An image resource or false on error
554
-	 */
555
-	public function loadFromFile($imagePath = false) {
556
-		// exif_imagetype throws "read error!" if file is less than 12 byte
557
-		if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
558
-			return false;
559
-		}
560
-		$iType = exif_imagetype($imagePath);
561
-		switch ($iType) {
562
-			case IMAGETYPE_GIF:
563
-				if (imagetypes() & IMG_GIF) {
564
-					$this->resource = imagecreatefromgif($imagePath);
565
-					// Preserve transparency
566
-					imagealphablending($this->resource, true);
567
-					imagesavealpha($this->resource, true);
568
-				} else {
569
-					$this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, ['app' => 'core']);
570
-				}
571
-				break;
572
-			case IMAGETYPE_JPEG:
573
-				if (imagetypes() & IMG_JPG) {
574
-					if (getimagesize($imagePath) !== false) {
575
-						$this->resource = @imagecreatefromjpeg($imagePath);
576
-					} else {
577
-						$this->logger->debug('OC_Image->loadFromFile, JPG image not valid: ' . $imagePath, ['app' => 'core']);
578
-					}
579
-				} else {
580
-					$this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, ['app' => 'core']);
581
-				}
582
-				break;
583
-			case IMAGETYPE_PNG:
584
-				if (imagetypes() & IMG_PNG) {
585
-					$this->resource = @imagecreatefrompng($imagePath);
586
-					// Preserve transparency
587
-					imagealphablending($this->resource, true);
588
-					imagesavealpha($this->resource, true);
589
-				} else {
590
-					$this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, ['app' => 'core']);
591
-				}
592
-				break;
593
-			case IMAGETYPE_XBM:
594
-				if (imagetypes() & IMG_XPM) {
595
-					$this->resource = @imagecreatefromxbm($imagePath);
596
-				} else {
597
-					$this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, ['app' => 'core']);
598
-				}
599
-				break;
600
-			case IMAGETYPE_WBMP:
601
-				if (imagetypes() & IMG_WBMP) {
602
-					$this->resource = @imagecreatefromwbmp($imagePath);
603
-				} else {
604
-					$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
605
-				}
606
-				break;
607
-			case IMAGETYPE_BMP:
608
-				$this->resource = $this->imagecreatefrombmp($imagePath);
609
-				break;
610
-			/*
48
+    /** @var false|resource */
49
+    protected $resource = false; // tmp resource.
50
+    /** @var int */
51
+    protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
52
+    /** @var string */
53
+    protected $mimeType = 'image/png'; // Default to png
54
+    /** @var int */
55
+    protected $bitDepth = 24;
56
+    /** @var null|string */
57
+    protected $filePath = null;
58
+    /** @var finfo */
59
+    private $fileInfo;
60
+    /** @var \OCP\ILogger */
61
+    private $logger;
62
+    /** @var \OCP\IConfig */
63
+    private $config;
64
+    /** @var array */
65
+    private $exif;
66
+
67
+    /**
68
+     * Constructor.
69
+     *
70
+     * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by
71
+     * an imagecreate* function.
72
+     * @param \OCP\ILogger $logger
73
+     * @param \OCP\IConfig $config
74
+     * @throws \InvalidArgumentException in case the $imageRef parameter is not null
75
+     */
76
+    public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) {
77
+        $this->logger = $logger;
78
+        if ($logger === null) {
79
+            $this->logger = \OC::$server->getLogger();
80
+        }
81
+        $this->config = $config;
82
+        if ($config === null) {
83
+            $this->config = \OC::$server->getConfig();
84
+        }
85
+
86
+        if (\OC_Util::fileInfoLoaded()) {
87
+            $this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
88
+        }
89
+
90
+        if ($imageRef !== null) {
91
+            throw new \InvalidArgumentException('The first parameter in the constructor is not supported anymore. Please use any of the load* methods of the image object to load an image.');
92
+        }
93
+    }
94
+
95
+    /**
96
+     * Determine whether the object contains an image resource.
97
+     *
98
+     * @return bool
99
+     */
100
+    public function valid() { // apparently you can't name a method 'empty'...
101
+        if (is_resource($this->resource)) {
102
+            return true;
103
+        }
104
+        if (is_object($this->resource) && get_class($this->resource) === 'GdImage') {
105
+            return true;
106
+        }
107
+
108
+        return false;
109
+    }
110
+
111
+    /**
112
+     * Returns the MIME type of the image or an empty string if no image is loaded.
113
+     *
114
+     * @return string
115
+     */
116
+    public function mimeType() {
117
+        return $this->valid() ? $this->mimeType : '';
118
+    }
119
+
120
+    /**
121
+     * Returns the width of the image or -1 if no image is loaded.
122
+     *
123
+     * @return int
124
+     */
125
+    public function width() {
126
+        return $this->valid() ? imagesx($this->resource) : -1;
127
+    }
128
+
129
+    /**
130
+     * Returns the height of the image or -1 if no image is loaded.
131
+     *
132
+     * @return int
133
+     */
134
+    public function height() {
135
+        return $this->valid() ? imagesy($this->resource) : -1;
136
+    }
137
+
138
+    /**
139
+     * Returns the width when the image orientation is top-left.
140
+     *
141
+     * @return int
142
+     */
143
+    public function widthTopLeft() {
144
+        $o = $this->getOrientation();
145
+        $this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, ['app' => 'core']);
146
+        switch ($o) {
147
+            case -1:
148
+            case 1:
149
+            case 2: // Not tested
150
+            case 3:
151
+            case 4: // Not tested
152
+                return $this->width();
153
+            case 5: // Not tested
154
+            case 6:
155
+            case 7: // Not tested
156
+            case 8:
157
+                return $this->height();
158
+        }
159
+        return $this->width();
160
+    }
161
+
162
+    /**
163
+     * Returns the height when the image orientation is top-left.
164
+     *
165
+     * @return int
166
+     */
167
+    public function heightTopLeft() {
168
+        $o = $this->getOrientation();
169
+        $this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, ['app' => 'core']);
170
+        switch ($o) {
171
+            case -1:
172
+            case 1:
173
+            case 2: // Not tested
174
+            case 3:
175
+            case 4: // Not tested
176
+                return $this->height();
177
+            case 5: // Not tested
178
+            case 6:
179
+            case 7: // Not tested
180
+            case 8:
181
+                return $this->width();
182
+        }
183
+        return $this->height();
184
+    }
185
+
186
+    /**
187
+     * Outputs the image.
188
+     *
189
+     * @param string $mimeType
190
+     * @return bool
191
+     */
192
+    public function show($mimeType = null) {
193
+        if ($mimeType === null) {
194
+            $mimeType = $this->mimeType();
195
+        }
196
+        header('Content-Type: ' . $mimeType);
197
+        return $this->_output(null, $mimeType);
198
+    }
199
+
200
+    /**
201
+     * Saves the image.
202
+     *
203
+     * @param string $filePath
204
+     * @param string $mimeType
205
+     * @return bool
206
+     */
207
+
208
+    public function save($filePath = null, $mimeType = null) {
209
+        if ($mimeType === null) {
210
+            $mimeType = $this->mimeType();
211
+        }
212
+        if ($filePath === null) {
213
+            if ($this->filePath === null) {
214
+                $this->logger->error(__METHOD__ . '(): called with no path.', ['app' => 'core']);
215
+                return false;
216
+            } else {
217
+                $filePath = $this->filePath;
218
+            }
219
+        }
220
+        return $this->_output($filePath, $mimeType);
221
+    }
222
+
223
+    /**
224
+     * Outputs/saves the image.
225
+     *
226
+     * @param string $filePath
227
+     * @param string $mimeType
228
+     * @return bool
229
+     * @throws Exception
230
+     */
231
+    private function _output($filePath = null, $mimeType = null) {
232
+        if ($filePath) {
233
+            if (!file_exists(dirname($filePath))) {
234
+                mkdir(dirname($filePath), 0777, true);
235
+            }
236
+            $isWritable = is_writable(dirname($filePath));
237
+            if (!$isWritable) {
238
+                $this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', ['app' => 'core']);
239
+                return false;
240
+            } elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
241
+                $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']);
242
+                return false;
243
+            }
244
+        }
245
+        if (!$this->valid()) {
246
+            return false;
247
+        }
248
+
249
+        $imageType = $this->imageType;
250
+        if ($mimeType !== null) {
251
+            switch ($mimeType) {
252
+                case 'image/gif':
253
+                    $imageType = IMAGETYPE_GIF;
254
+                    break;
255
+                case 'image/jpeg':
256
+                    $imageType = IMAGETYPE_JPEG;
257
+                    break;
258
+                case 'image/png':
259
+                    $imageType = IMAGETYPE_PNG;
260
+                    break;
261
+                case 'image/x-xbitmap':
262
+                    $imageType = IMAGETYPE_XBM;
263
+                    break;
264
+                case 'image/bmp':
265
+                case 'image/x-ms-bmp':
266
+                    $imageType = IMAGETYPE_BMP;
267
+                    break;
268
+                default:
269
+                    throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
270
+            }
271
+        }
272
+
273
+        switch ($imageType) {
274
+            case IMAGETYPE_GIF:
275
+                $retVal = imagegif($this->resource, $filePath);
276
+                break;
277
+            case IMAGETYPE_JPEG:
278
+                $retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
279
+                break;
280
+            case IMAGETYPE_PNG:
281
+                $retVal = imagepng($this->resource, $filePath);
282
+                break;
283
+            case IMAGETYPE_XBM:
284
+                if (function_exists('imagexbm')) {
285
+                    $retVal = imagexbm($this->resource, $filePath);
286
+                } else {
287
+                    throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
288
+                }
289
+
290
+                break;
291
+            case IMAGETYPE_WBMP:
292
+                $retVal = imagewbmp($this->resource, $filePath);
293
+                break;
294
+            case IMAGETYPE_BMP:
295
+                $retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
296
+                break;
297
+            default:
298
+                $retVal = imagepng($this->resource, $filePath);
299
+        }
300
+        return $retVal;
301
+    }
302
+
303
+    /**
304
+     * Prints the image when called as $image().
305
+     */
306
+    public function __invoke() {
307
+        return $this->show();
308
+    }
309
+
310
+    /**
311
+     * @param resource Returns the image resource in any.
312
+     * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd"
313
+     */
314
+    public function setResource($resource) {
315
+        // For PHP<8
316
+        if (is_resource($resource) && get_resource_type($resource) === 'gd') {
317
+            $this->resource = $resource;
318
+            return;
319
+        }
320
+        // PHP 8 has real objects for GD stuff
321
+        if (is_object($resource) && get_class($resource) === 'GdImage') {
322
+            $this->resource = $resource;
323
+            return;
324
+        }
325
+        throw new \InvalidArgumentException('Supplied resource is not of type "gd".');
326
+    }
327
+
328
+    /**
329
+     * @return resource Returns the image resource in any.
330
+     */
331
+    public function resource() {
332
+        return $this->resource;
333
+    }
334
+
335
+    /**
336
+     * @return string Returns the mimetype of the data. Returns the empty string
337
+     * if the data is not valid.
338
+     */
339
+    public function dataMimeType() {
340
+        if (!$this->valid()) {
341
+            return '';
342
+        }
343
+
344
+        switch ($this->mimeType) {
345
+            case 'image/png':
346
+            case 'image/jpeg':
347
+            case 'image/gif':
348
+                return $this->mimeType;
349
+            default:
350
+                return 'image/png';
351
+        }
352
+    }
353
+
354
+    /**
355
+     * @return null|string Returns the raw image data.
356
+     */
357
+    public function data() {
358
+        if (!$this->valid()) {
359
+            return null;
360
+        }
361
+        ob_start();
362
+        switch ($this->mimeType) {
363
+            case "image/png":
364
+                $res = imagepng($this->resource);
365
+                break;
366
+            case "image/jpeg":
367
+                $quality = $this->getJpegQuality();
368
+                if ($quality !== null) {
369
+                    $res = imagejpeg($this->resource, null, $quality);
370
+                } else {
371
+                    $res = imagejpeg($this->resource);
372
+                }
373
+                break;
374
+            case "image/gif":
375
+                $res = imagegif($this->resource);
376
+                break;
377
+            default:
378
+                $res = imagepng($this->resource);
379
+                $this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', ['app' => 'core']);
380
+                break;
381
+        }
382
+        if (!$res) {
383
+            $this->logger->error('OC_Image->data. Error getting image data.', ['app' => 'core']);
384
+        }
385
+        return ob_get_clean();
386
+    }
387
+
388
+    /**
389
+     * @return string - base64 encoded, which is suitable for embedding in a VCard.
390
+     */
391
+    public function __toString() {
392
+        return base64_encode($this->data());
393
+    }
394
+
395
+    /**
396
+     * @return int|null
397
+     */
398
+    protected function getJpegQuality() {
399
+        $quality = $this->config->getAppValue('preview', 'jpeg_quality', 90);
400
+        if ($quality !== null) {
401
+            $quality = min(100, max(10, (int) $quality));
402
+        }
403
+        return $quality;
404
+    }
405
+
406
+    /**
407
+     * (I'm open for suggestions on better method name ;)
408
+     * Get the orientation based on EXIF data.
409
+     *
410
+     * @return int The orientation or -1 if no EXIF data is available.
411
+     */
412
+    public function getOrientation() {
413
+        if ($this->exif !== null) {
414
+            return $this->exif['Orientation'];
415
+        }
416
+
417
+        if ($this->imageType !== IMAGETYPE_JPEG) {
418
+            $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', ['app' => 'core']);
419
+            return -1;
420
+        }
421
+        if (!is_callable('exif_read_data')) {
422
+            $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
423
+            return -1;
424
+        }
425
+        if (!$this->valid()) {
426
+            $this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
427
+            return -1;
428
+        }
429
+        if (is_null($this->filePath) || !is_readable($this->filePath)) {
430
+            $this->logger->debug('OC_Image->fixOrientation() No readable file path set.', ['app' => 'core']);
431
+            return -1;
432
+        }
433
+        $exif = @exif_read_data($this->filePath, 'IFD0');
434
+        if (!$exif) {
435
+            return -1;
436
+        }
437
+        if (!isset($exif['Orientation'])) {
438
+            return -1;
439
+        }
440
+        $this->exif = $exif;
441
+        return $exif['Orientation'];
442
+    }
443
+
444
+    public function readExif($data) {
445
+        if (!is_callable('exif_read_data')) {
446
+            $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
447
+            return;
448
+        }
449
+        if (!$this->valid()) {
450
+            $this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
451
+            return;
452
+        }
453
+
454
+        $exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
455
+        if (!$exif) {
456
+            return;
457
+        }
458
+        if (!isset($exif['Orientation'])) {
459
+            return;
460
+        }
461
+        $this->exif = $exif;
462
+    }
463
+
464
+    /**
465
+     * (I'm open for suggestions on better method name ;)
466
+     * Fixes orientation based on EXIF data.
467
+     *
468
+     * @return bool
469
+     */
470
+    public function fixOrientation() {
471
+        $o = $this->getOrientation();
472
+        $this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, ['app' => 'core']);
473
+        $rotate = 0;
474
+        $flip = false;
475
+        switch ($o) {
476
+            case -1:
477
+                return false; //Nothing to fix
478
+            case 1:
479
+                $rotate = 0;
480
+                break;
481
+            case 2:
482
+                $rotate = 0;
483
+                $flip = true;
484
+                break;
485
+            case 3:
486
+                $rotate = 180;
487
+                break;
488
+            case 4:
489
+                $rotate = 180;
490
+                $flip = true;
491
+                break;
492
+            case 5:
493
+                $rotate = 90;
494
+                $flip = true;
495
+                break;
496
+            case 6:
497
+                $rotate = 270;
498
+                break;
499
+            case 7:
500
+                $rotate = 270;
501
+                $flip = true;
502
+                break;
503
+            case 8:
504
+                $rotate = 90;
505
+                break;
506
+        }
507
+        if ($flip && function_exists('imageflip')) {
508
+            imageflip($this->resource, IMG_FLIP_HORIZONTAL);
509
+        }
510
+        if ($rotate) {
511
+            $res = imagerotate($this->resource, $rotate, 0);
512
+            if ($res) {
513
+                if (imagealphablending($res, true)) {
514
+                    if (imagesavealpha($res, true)) {
515
+                        imagedestroy($this->resource);
516
+                        $this->resource = $res;
517
+                        return true;
518
+                    } else {
519
+                        $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']);
520
+                        return false;
521
+                    }
522
+                } else {
523
+                    $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']);
524
+                    return false;
525
+                }
526
+            } else {
527
+                $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']);
528
+                return false;
529
+            }
530
+        }
531
+        return false;
532
+    }
533
+
534
+    /**
535
+     * Loads an image from an open file handle.
536
+     * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
537
+     *
538
+     * @param resource $handle
539
+     * @return resource|false An image resource or false on error
540
+     */
541
+    public function loadFromFileHandle($handle) {
542
+        $contents = stream_get_contents($handle);
543
+        if ($this->loadFromData($contents)) {
544
+            return $this->resource;
545
+        }
546
+        return false;
547
+    }
548
+
549
+    /**
550
+     * Loads an image from a local file.
551
+     *
552
+     * @param bool|string $imagePath The path to a local file.
553
+     * @return bool|resource An image resource or false on error
554
+     */
555
+    public function loadFromFile($imagePath = false) {
556
+        // exif_imagetype throws "read error!" if file is less than 12 byte
557
+        if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
558
+            return false;
559
+        }
560
+        $iType = exif_imagetype($imagePath);
561
+        switch ($iType) {
562
+            case IMAGETYPE_GIF:
563
+                if (imagetypes() & IMG_GIF) {
564
+                    $this->resource = imagecreatefromgif($imagePath);
565
+                    // Preserve transparency
566
+                    imagealphablending($this->resource, true);
567
+                    imagesavealpha($this->resource, true);
568
+                } else {
569
+                    $this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, ['app' => 'core']);
570
+                }
571
+                break;
572
+            case IMAGETYPE_JPEG:
573
+                if (imagetypes() & IMG_JPG) {
574
+                    if (getimagesize($imagePath) !== false) {
575
+                        $this->resource = @imagecreatefromjpeg($imagePath);
576
+                    } else {
577
+                        $this->logger->debug('OC_Image->loadFromFile, JPG image not valid: ' . $imagePath, ['app' => 'core']);
578
+                    }
579
+                } else {
580
+                    $this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, ['app' => 'core']);
581
+                }
582
+                break;
583
+            case IMAGETYPE_PNG:
584
+                if (imagetypes() & IMG_PNG) {
585
+                    $this->resource = @imagecreatefrompng($imagePath);
586
+                    // Preserve transparency
587
+                    imagealphablending($this->resource, true);
588
+                    imagesavealpha($this->resource, true);
589
+                } else {
590
+                    $this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, ['app' => 'core']);
591
+                }
592
+                break;
593
+            case IMAGETYPE_XBM:
594
+                if (imagetypes() & IMG_XPM) {
595
+                    $this->resource = @imagecreatefromxbm($imagePath);
596
+                } else {
597
+                    $this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, ['app' => 'core']);
598
+                }
599
+                break;
600
+            case IMAGETYPE_WBMP:
601
+                if (imagetypes() & IMG_WBMP) {
602
+                    $this->resource = @imagecreatefromwbmp($imagePath);
603
+                } else {
604
+                    $this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
605
+                }
606
+                break;
607
+            case IMAGETYPE_BMP:
608
+                $this->resource = $this->imagecreatefrombmp($imagePath);
609
+                break;
610
+            /*
611 611
 			case IMAGETYPE_TIFF_II: // (intel byte order)
612 612
 				break;
613 613
 			case IMAGETYPE_TIFF_MM: // (motorola byte order)
@@ -631,671 +631,671 @@  discard block
 block discarded – undo
631 631
 			case IMAGETYPE_PSD:
632 632
 				break;
633 633
 			*/
634
-			default:
635
-
636
-				// this is mostly file created from encrypted file
637
-				$this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath)));
638
-				$iType = IMAGETYPE_PNG;
639
-				$this->logger->debug('OC_Image->loadFromFile, Default', ['app' => 'core']);
640
-				break;
641
-		}
642
-		if ($this->valid()) {
643
-			$this->imageType = $iType;
644
-			$this->mimeType = image_type_to_mime_type($iType);
645
-			$this->filePath = $imagePath;
646
-		}
647
-		return $this->resource;
648
-	}
649
-
650
-	/**
651
-	 * Loads an image from a string of data.
652
-	 *
653
-	 * @param string $str A string of image data as read from a file.
654
-	 * @return bool|resource An image resource or false on error
655
-	 */
656
-	public function loadFromData($str) {
657
-		if (is_resource($str)) {
658
-			return false;
659
-		}
660
-		$this->resource = @imagecreatefromstring($str);
661
-		if ($this->fileInfo) {
662
-			$this->mimeType = $this->fileInfo->buffer($str);
663
-		}
664
-		if (is_resource($this->resource)) {
665
-			imagealphablending($this->resource, false);
666
-			imagesavealpha($this->resource, true);
667
-		}
668
-
669
-		if (!$this->resource) {
670
-			$this->logger->debug('OC_Image->loadFromFile, could not load', ['app' => 'core']);
671
-			return false;
672
-		}
673
-		return $this->resource;
674
-	}
675
-
676
-	/**
677
-	 * Loads an image from a base64 encoded string.
678
-	 *
679
-	 * @param string $str A string base64 encoded string of image data.
680
-	 * @return bool|resource An image resource or false on error
681
-	 */
682
-	public function loadFromBase64($str) {
683
-		if (!is_string($str)) {
684
-			return false;
685
-		}
686
-		$data = base64_decode($str);
687
-		if ($data) { // try to load from string data
688
-			$this->resource = @imagecreatefromstring($data);
689
-			if ($this->fileInfo) {
690
-				$this->mimeType = $this->fileInfo->buffer($data);
691
-			}
692
-			if (!$this->resource) {
693
-				$this->logger->debug('OC_Image->loadFromBase64, could not load', ['app' => 'core']);
694
-				return false;
695
-			}
696
-			return $this->resource;
697
-		} else {
698
-			return false;
699
-		}
700
-	}
701
-
702
-	/**
703
-	 * Create a new image from file or URL
704
-	 *
705
-	 * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
706
-	 * @version 1.00
707
-	 * @param string $fileName <p>
708
-	 * Path to the BMP image.
709
-	 * </p>
710
-	 * @return bool|resource an image resource identifier on success, <b>FALSE</b> on errors.
711
-	 */
712
-	private function imagecreatefrombmp($fileName) {
713
-		if (!($fh = fopen($fileName, 'rb'))) {
714
-			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, ['app' => 'core']);
715
-			return false;
716
-		}
717
-		// read file header
718
-		$meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
719
-		// check for bitmap
720
-		if ($meta['type'] != 19778) {
721
-			fclose($fh);
722
-			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
723
-			return false;
724
-		}
725
-		// read image header
726
-		$meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
727
-		// read additional 16bit header
728
-		if ($meta['bits'] == 16) {
729
-			$meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
730
-		}
731
-		// set bytes and padding
732
-		$meta['bytes'] = $meta['bits'] / 8;
733
-		$this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
734
-		$meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
735
-		if ($meta['decal'] == 4) {
736
-			$meta['decal'] = 0;
737
-		}
738
-		// obtain imagesize
739
-		if ($meta['imagesize'] < 1) {
740
-			$meta['imagesize'] = $meta['filesize'] - $meta['offset'];
741
-			// in rare cases filesize is equal to offset so we need to read physical size
742
-			if ($meta['imagesize'] < 1) {
743
-				$meta['imagesize'] = @filesize($fileName) - $meta['offset'];
744
-				if ($meta['imagesize'] < 1) {
745
-					fclose($fh);
746
-					$this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
747
-					return false;
748
-				}
749
-			}
750
-		}
751
-		// calculate colors
752
-		$meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
753
-		// read color palette
754
-		$palette = [];
755
-		if ($meta['bits'] < 16) {
756
-			$palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
757
-			// in rare cases the color value is signed
758
-			if ($palette[1] < 0) {
759
-				foreach ($palette as $i => $color) {
760
-					$palette[$i] = $color + 16777216;
761
-				}
762
-			}
763
-		}
764
-		// create gd image
765
-		$im = imagecreatetruecolor($meta['width'], $meta['height']);
766
-		if ($im == false) {
767
-			fclose($fh);
768
-			$this->logger->warning(
769
-				'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
770
-				['app' => 'core']);
771
-			return false;
772
-		}
773
-
774
-		$data = fread($fh, $meta['imagesize']);
775
-		$p = 0;
776
-		$vide = chr(0);
777
-		$y = $meta['height'] - 1;
778
-		$error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
779
-		// loop through the image data beginning with the lower left corner
780
-		while ($y >= 0) {
781
-			$x = 0;
782
-			while ($x < $meta['width']) {
783
-				switch ($meta['bits']) {
784
-					case 32:
785
-					case 24:
786
-						if (!($part = substr($data, $p, 3))) {
787
-							$this->logger->warning($error, ['app' => 'core']);
788
-							return $im;
789
-						}
790
-						$color = @unpack('V', $part . $vide);
791
-						break;
792
-					case 16:
793
-						if (!($part = substr($data, $p, 2))) {
794
-							fclose($fh);
795
-							$this->logger->warning($error, ['app' => 'core']);
796
-							return $im;
797
-						}
798
-						$color = @unpack('v', $part);
799
-						$color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
800
-						break;
801
-					case 8:
802
-						$color = @unpack('n', $vide . ($data[$p] ?? ''));
803
-						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
804
-						break;
805
-					case 4:
806
-						$color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
807
-						$color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
808
-						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
809
-						break;
810
-					case 1:
811
-						$color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
812
-						switch (($p * 8) % 8) {
813
-							case 0:
814
-								$color[1] = $color[1] >> 7;
815
-								break;
816
-							case 1:
817
-								$color[1] = ($color[1] & 0x40) >> 6;
818
-								break;
819
-							case 2:
820
-								$color[1] = ($color[1] & 0x20) >> 5;
821
-								break;
822
-							case 3:
823
-								$color[1] = ($color[1] & 0x10) >> 4;
824
-								break;
825
-							case 4:
826
-								$color[1] = ($color[1] & 0x8) >> 3;
827
-								break;
828
-							case 5:
829
-								$color[1] = ($color[1] & 0x4) >> 2;
830
-								break;
831
-							case 6:
832
-								$color[1] = ($color[1] & 0x2) >> 1;
833
-								break;
834
-							case 7:
835
-								$color[1] = ($color[1] & 0x1);
836
-								break;
837
-						}
838
-						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
839
-						break;
840
-					default:
841
-						fclose($fh);
842
-						$this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', ['app' => 'core']);
843
-						return false;
844
-				}
845
-				imagesetpixel($im, $x, $y, $color[1]);
846
-				$x++;
847
-				$p += $meta['bytes'];
848
-			}
849
-			$y--;
850
-			$p += $meta['decal'];
851
-		}
852
-		fclose($fh);
853
-		return $im;
854
-	}
855
-
856
-	/**
857
-	 * Resizes the image preserving ratio.
858
-	 *
859
-	 * @param integer $maxSize The maximum size of either the width or height.
860
-	 * @return bool
861
-	 */
862
-	public function resize($maxSize) {
863
-		$result = $this->resizeNew($maxSize);
864
-		imagedestroy($this->resource);
865
-		$this->resource = $result;
866
-		return is_resource($result);
867
-	}
868
-
869
-	/**
870
-	 * @param $maxSize
871
-	 * @return resource | bool
872
-	 */
873
-	private function resizeNew($maxSize) {
874
-		if (!$this->valid()) {
875
-			$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
876
-			return false;
877
-		}
878
-		$widthOrig = imagesx($this->resource);
879
-		$heightOrig = imagesy($this->resource);
880
-		$ratioOrig = $widthOrig / $heightOrig;
881
-
882
-		if ($ratioOrig > 1) {
883
-			$newHeight = round($maxSize / $ratioOrig);
884
-			$newWidth = $maxSize;
885
-		} else {
886
-			$newWidth = round($maxSize * $ratioOrig);
887
-			$newHeight = $maxSize;
888
-		}
889
-
890
-		return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight));
891
-	}
892
-
893
-	/**
894
-	 * @param int $width
895
-	 * @param int $height
896
-	 * @return bool
897
-	 */
898
-	public function preciseResize(int $width, int $height): bool {
899
-		$result = $this->preciseResizeNew($width, $height);
900
-		imagedestroy($this->resource);
901
-		$this->resource = $result;
902
-		return is_resource($result);
903
-	}
904
-
905
-
906
-	/**
907
-	 * @param int $width
908
-	 * @param int $height
909
-	 * @return resource | bool
910
-	 */
911
-	public function preciseResizeNew(int $width, int $height) {
912
-		if (!$this->valid()) {
913
-			$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
914
-			return false;
915
-		}
916
-		$widthOrig = imagesx($this->resource);
917
-		$heightOrig = imagesy($this->resource);
918
-		$process = imagecreatetruecolor($width, $height);
919
-		if ($process === false) {
920
-			$this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
921
-			return false;
922
-		}
923
-
924
-		// preserve transparency
925
-		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
926
-			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
927
-			imagealphablending($process, false);
928
-			imagesavealpha($process, true);
929
-		}
930
-
931
-		$res = imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
932
-		if ($res === false) {
933
-			$this->logger->error(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']);
934
-			imagedestroy($process);
935
-			return false;
936
-		}
937
-		return $process;
938
-	}
939
-
940
-	/**
941
-	 * Crops the image to the middle square. If the image is already square it just returns.
942
-	 *
943
-	 * @param int $size maximum size for the result (optional)
944
-	 * @return bool for success or failure
945
-	 */
946
-	public function centerCrop($size = 0) {
947
-		if (!$this->valid()) {
948
-			$this->logger->error('OC_Image->centerCrop, No image loaded', ['app' => 'core']);
949
-			return false;
950
-		}
951
-		$widthOrig = imagesx($this->resource);
952
-		$heightOrig = imagesy($this->resource);
953
-		if ($widthOrig === $heightOrig and $size == 0) {
954
-			return true;
955
-		}
956
-		$ratioOrig = $widthOrig / $heightOrig;
957
-		$width = $height = min($widthOrig, $heightOrig);
958
-
959
-		if ($ratioOrig > 1) {
960
-			$x = ($widthOrig / 2) - ($width / 2);
961
-			$y = 0;
962
-		} else {
963
-			$y = ($heightOrig / 2) - ($height / 2);
964
-			$x = 0;
965
-		}
966
-		if ($size > 0) {
967
-			$targetWidth = $size;
968
-			$targetHeight = $size;
969
-		} else {
970
-			$targetWidth = $width;
971
-			$targetHeight = $height;
972
-		}
973
-		$process = imagecreatetruecolor($targetWidth, $targetHeight);
974
-		if ($process == false) {
975
-			$this->logger->error('OC_Image->centerCrop, Error creating true color image', ['app' => 'core']);
976
-			imagedestroy($process);
977
-			return false;
978
-		}
979
-
980
-		// preserve transparency
981
-		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
982
-			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
983
-			imagealphablending($process, false);
984
-			imagesavealpha($process, true);
985
-		}
986
-
987
-		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
988
-		if ($process == false) {
989
-			$this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']);
990
-			imagedestroy($process);
991
-			return false;
992
-		}
993
-		imagedestroy($this->resource);
994
-		$this->resource = $process;
995
-		return true;
996
-	}
997
-
998
-	/**
999
-	 * Crops the image from point $x$y with dimension $wx$h.
1000
-	 *
1001
-	 * @param int $x Horizontal position
1002
-	 * @param int $y Vertical position
1003
-	 * @param int $w Width
1004
-	 * @param int $h Height
1005
-	 * @return bool for success or failure
1006
-	 */
1007
-	public function crop(int $x, int $y, int $w, int $h): bool {
1008
-		$result = $this->cropNew($x, $y, $w, $h);
1009
-		imagedestroy($this->resource);
1010
-		$this->resource = $result;
1011
-		return is_resource($result);
1012
-	}
1013
-
1014
-	/**
1015
-	 * Crops the image from point $x$y with dimension $wx$h.
1016
-	 *
1017
-	 * @param int $x Horizontal position
1018
-	 * @param int $y Vertical position
1019
-	 * @param int $w Width
1020
-	 * @param int $h Height
1021
-	 * @return resource | bool
1022
-	 */
1023
-	public function cropNew(int $x, int $y, int $w, int $h) {
1024
-		if (!$this->valid()) {
1025
-			$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1026
-			return false;
1027
-		}
1028
-		$process = imagecreatetruecolor($w, $h);
1029
-		if ($process == false) {
1030
-			$this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
1031
-			imagedestroy($process);
1032
-			return false;
1033
-		}
1034
-
1035
-		// preserve transparency
1036
-		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1037
-			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1038
-			imagealphablending($process, false);
1039
-			imagesavealpha($process, true);
1040
-		}
1041
-
1042
-		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
1043
-		if ($process == false) {
1044
-			$this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, ['app' => 'core']);
1045
-			imagedestroy($process);
1046
-			return false;
1047
-		}
1048
-		return $process;
1049
-	}
1050
-
1051
-	/**
1052
-	 * Resizes the image to fit within a boundary while preserving ratio.
1053
-	 *
1054
-	 * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
1055
-	 *
1056
-	 * @param integer $maxWidth
1057
-	 * @param integer $maxHeight
1058
-	 * @return bool
1059
-	 */
1060
-	public function fitIn($maxWidth, $maxHeight) {
1061
-		if (!$this->valid()) {
1062
-			$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1063
-			return false;
1064
-		}
1065
-		$widthOrig = imagesx($this->resource);
1066
-		$heightOrig = imagesy($this->resource);
1067
-		$ratio = $widthOrig / $heightOrig;
1068
-
1069
-		$newWidth = min($maxWidth, $ratio * $maxHeight);
1070
-		$newHeight = min($maxHeight, $maxWidth / $ratio);
1071
-
1072
-		$this->preciseResize((int)round($newWidth), (int)round($newHeight));
1073
-		return true;
1074
-	}
1075
-
1076
-	/**
1077
-	 * Shrinks larger images to fit within specified boundaries while preserving ratio.
1078
-	 *
1079
-	 * @param integer $maxWidth
1080
-	 * @param integer $maxHeight
1081
-	 * @return bool
1082
-	 */
1083
-	public function scaleDownToFit($maxWidth, $maxHeight) {
1084
-		if (!$this->valid()) {
1085
-			$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1086
-			return false;
1087
-		}
1088
-		$widthOrig = imagesx($this->resource);
1089
-		$heightOrig = imagesy($this->resource);
1090
-
1091
-		if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
1092
-			return $this->fitIn($maxWidth, $maxHeight);
1093
-		}
1094
-
1095
-		return false;
1096
-	}
1097
-
1098
-	public function copy(): IImage {
1099
-		$image = new OC_Image(null, $this->logger, $this->config);
1100
-		$image->resource = imagecreatetruecolor($this->width(), $this->height());
1101
-		imagecopy(
1102
-			$image->resource(),
1103
-			$this->resource(),
1104
-			0,
1105
-			0,
1106
-			0,
1107
-			0,
1108
-			$this->width(),
1109
-			$this->height()
1110
-		);
1111
-
1112
-		return $image;
1113
-	}
1114
-
1115
-	public function cropCopy(int $x, int $y, int $w, int $h): IImage {
1116
-		$image = new OC_Image(null, $this->logger, $this->config);
1117
-		$image->imageType = $this->imageType;
1118
-		$image->mimeType = $this->mimeType;
1119
-		$image->bitDepth = $this->bitDepth;
1120
-		$image->resource = $this->cropNew($x, $y, $w, $h);
1121
-
1122
-		return $image;
1123
-	}
1124
-
1125
-	public function preciseResizeCopy(int $width, int $height): IImage {
1126
-		$image = new OC_Image(null, $this->logger, $this->config);
1127
-		$image->imageType = $this->imageType;
1128
-		$image->mimeType = $this->mimeType;
1129
-		$image->bitDepth = $this->bitDepth;
1130
-		$image->resource = $this->preciseResizeNew($width, $height);
1131
-
1132
-		return $image;
1133
-	}
1134
-
1135
-	public function resizeCopy(int $maxSize): IImage {
1136
-		$image = new OC_Image(null, $this->logger, $this->config);
1137
-		$image->imageType = $this->imageType;
1138
-		$image->mimeType = $this->mimeType;
1139
-		$image->bitDepth = $this->bitDepth;
1140
-		$image->resource = $this->resizeNew($maxSize);
1141
-
1142
-		return $image;
1143
-	}
1144
-
1145
-
1146
-	/**
1147
-	 * Resizes the image preserving ratio, returning a new copy
1148
-	 *
1149
-	 * @param integer $maxSize The maximum size of either the width or height.
1150
-	 * @return bool
1151
-	 */
1152
-	public function copyResize($maxSize): IImage {
1153
-	}
1154
-
1155
-	/**
1156
-	 * Destroys the current image and resets the object
1157
-	 */
1158
-	public function destroy() {
1159
-		if ($this->valid()) {
1160
-			imagedestroy($this->resource);
1161
-		}
1162
-		$this->resource = null;
1163
-	}
1164
-
1165
-	public function __destruct() {
1166
-		$this->destroy();
1167
-	}
634
+            default:
635
+
636
+                // this is mostly file created from encrypted file
637
+                $this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath)));
638
+                $iType = IMAGETYPE_PNG;
639
+                $this->logger->debug('OC_Image->loadFromFile, Default', ['app' => 'core']);
640
+                break;
641
+        }
642
+        if ($this->valid()) {
643
+            $this->imageType = $iType;
644
+            $this->mimeType = image_type_to_mime_type($iType);
645
+            $this->filePath = $imagePath;
646
+        }
647
+        return $this->resource;
648
+    }
649
+
650
+    /**
651
+     * Loads an image from a string of data.
652
+     *
653
+     * @param string $str A string of image data as read from a file.
654
+     * @return bool|resource An image resource or false on error
655
+     */
656
+    public function loadFromData($str) {
657
+        if (is_resource($str)) {
658
+            return false;
659
+        }
660
+        $this->resource = @imagecreatefromstring($str);
661
+        if ($this->fileInfo) {
662
+            $this->mimeType = $this->fileInfo->buffer($str);
663
+        }
664
+        if (is_resource($this->resource)) {
665
+            imagealphablending($this->resource, false);
666
+            imagesavealpha($this->resource, true);
667
+        }
668
+
669
+        if (!$this->resource) {
670
+            $this->logger->debug('OC_Image->loadFromFile, could not load', ['app' => 'core']);
671
+            return false;
672
+        }
673
+        return $this->resource;
674
+    }
675
+
676
+    /**
677
+     * Loads an image from a base64 encoded string.
678
+     *
679
+     * @param string $str A string base64 encoded string of image data.
680
+     * @return bool|resource An image resource or false on error
681
+     */
682
+    public function loadFromBase64($str) {
683
+        if (!is_string($str)) {
684
+            return false;
685
+        }
686
+        $data = base64_decode($str);
687
+        if ($data) { // try to load from string data
688
+            $this->resource = @imagecreatefromstring($data);
689
+            if ($this->fileInfo) {
690
+                $this->mimeType = $this->fileInfo->buffer($data);
691
+            }
692
+            if (!$this->resource) {
693
+                $this->logger->debug('OC_Image->loadFromBase64, could not load', ['app' => 'core']);
694
+                return false;
695
+            }
696
+            return $this->resource;
697
+        } else {
698
+            return false;
699
+        }
700
+    }
701
+
702
+    /**
703
+     * Create a new image from file or URL
704
+     *
705
+     * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
706
+     * @version 1.00
707
+     * @param string $fileName <p>
708
+     * Path to the BMP image.
709
+     * </p>
710
+     * @return bool|resource an image resource identifier on success, <b>FALSE</b> on errors.
711
+     */
712
+    private function imagecreatefrombmp($fileName) {
713
+        if (!($fh = fopen($fileName, 'rb'))) {
714
+            $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, ['app' => 'core']);
715
+            return false;
716
+        }
717
+        // read file header
718
+        $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
719
+        // check for bitmap
720
+        if ($meta['type'] != 19778) {
721
+            fclose($fh);
722
+            $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
723
+            return false;
724
+        }
725
+        // read image header
726
+        $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
727
+        // read additional 16bit header
728
+        if ($meta['bits'] == 16) {
729
+            $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
730
+        }
731
+        // set bytes and padding
732
+        $meta['bytes'] = $meta['bits'] / 8;
733
+        $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
734
+        $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
735
+        if ($meta['decal'] == 4) {
736
+            $meta['decal'] = 0;
737
+        }
738
+        // obtain imagesize
739
+        if ($meta['imagesize'] < 1) {
740
+            $meta['imagesize'] = $meta['filesize'] - $meta['offset'];
741
+            // in rare cases filesize is equal to offset so we need to read physical size
742
+            if ($meta['imagesize'] < 1) {
743
+                $meta['imagesize'] = @filesize($fileName) - $meta['offset'];
744
+                if ($meta['imagesize'] < 1) {
745
+                    fclose($fh);
746
+                    $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
747
+                    return false;
748
+                }
749
+            }
750
+        }
751
+        // calculate colors
752
+        $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
753
+        // read color palette
754
+        $palette = [];
755
+        if ($meta['bits'] < 16) {
756
+            $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
757
+            // in rare cases the color value is signed
758
+            if ($palette[1] < 0) {
759
+                foreach ($palette as $i => $color) {
760
+                    $palette[$i] = $color + 16777216;
761
+                }
762
+            }
763
+        }
764
+        // create gd image
765
+        $im = imagecreatetruecolor($meta['width'], $meta['height']);
766
+        if ($im == false) {
767
+            fclose($fh);
768
+            $this->logger->warning(
769
+                'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
770
+                ['app' => 'core']);
771
+            return false;
772
+        }
773
+
774
+        $data = fread($fh, $meta['imagesize']);
775
+        $p = 0;
776
+        $vide = chr(0);
777
+        $y = $meta['height'] - 1;
778
+        $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
779
+        // loop through the image data beginning with the lower left corner
780
+        while ($y >= 0) {
781
+            $x = 0;
782
+            while ($x < $meta['width']) {
783
+                switch ($meta['bits']) {
784
+                    case 32:
785
+                    case 24:
786
+                        if (!($part = substr($data, $p, 3))) {
787
+                            $this->logger->warning($error, ['app' => 'core']);
788
+                            return $im;
789
+                        }
790
+                        $color = @unpack('V', $part . $vide);
791
+                        break;
792
+                    case 16:
793
+                        if (!($part = substr($data, $p, 2))) {
794
+                            fclose($fh);
795
+                            $this->logger->warning($error, ['app' => 'core']);
796
+                            return $im;
797
+                        }
798
+                        $color = @unpack('v', $part);
799
+                        $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
800
+                        break;
801
+                    case 8:
802
+                        $color = @unpack('n', $vide . ($data[$p] ?? ''));
803
+                        $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
804
+                        break;
805
+                    case 4:
806
+                        $color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
807
+                        $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
808
+                        $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
809
+                        break;
810
+                    case 1:
811
+                        $color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
812
+                        switch (($p * 8) % 8) {
813
+                            case 0:
814
+                                $color[1] = $color[1] >> 7;
815
+                                break;
816
+                            case 1:
817
+                                $color[1] = ($color[1] & 0x40) >> 6;
818
+                                break;
819
+                            case 2:
820
+                                $color[1] = ($color[1] & 0x20) >> 5;
821
+                                break;
822
+                            case 3:
823
+                                $color[1] = ($color[1] & 0x10) >> 4;
824
+                                break;
825
+                            case 4:
826
+                                $color[1] = ($color[1] & 0x8) >> 3;
827
+                                break;
828
+                            case 5:
829
+                                $color[1] = ($color[1] & 0x4) >> 2;
830
+                                break;
831
+                            case 6:
832
+                                $color[1] = ($color[1] & 0x2) >> 1;
833
+                                break;
834
+                            case 7:
835
+                                $color[1] = ($color[1] & 0x1);
836
+                                break;
837
+                        }
838
+                        $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
839
+                        break;
840
+                    default:
841
+                        fclose($fh);
842
+                        $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', ['app' => 'core']);
843
+                        return false;
844
+                }
845
+                imagesetpixel($im, $x, $y, $color[1]);
846
+                $x++;
847
+                $p += $meta['bytes'];
848
+            }
849
+            $y--;
850
+            $p += $meta['decal'];
851
+        }
852
+        fclose($fh);
853
+        return $im;
854
+    }
855
+
856
+    /**
857
+     * Resizes the image preserving ratio.
858
+     *
859
+     * @param integer $maxSize The maximum size of either the width or height.
860
+     * @return bool
861
+     */
862
+    public function resize($maxSize) {
863
+        $result = $this->resizeNew($maxSize);
864
+        imagedestroy($this->resource);
865
+        $this->resource = $result;
866
+        return is_resource($result);
867
+    }
868
+
869
+    /**
870
+     * @param $maxSize
871
+     * @return resource | bool
872
+     */
873
+    private function resizeNew($maxSize) {
874
+        if (!$this->valid()) {
875
+            $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
876
+            return false;
877
+        }
878
+        $widthOrig = imagesx($this->resource);
879
+        $heightOrig = imagesy($this->resource);
880
+        $ratioOrig = $widthOrig / $heightOrig;
881
+
882
+        if ($ratioOrig > 1) {
883
+            $newHeight = round($maxSize / $ratioOrig);
884
+            $newWidth = $maxSize;
885
+        } else {
886
+            $newWidth = round($maxSize * $ratioOrig);
887
+            $newHeight = $maxSize;
888
+        }
889
+
890
+        return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight));
891
+    }
892
+
893
+    /**
894
+     * @param int $width
895
+     * @param int $height
896
+     * @return bool
897
+     */
898
+    public function preciseResize(int $width, int $height): bool {
899
+        $result = $this->preciseResizeNew($width, $height);
900
+        imagedestroy($this->resource);
901
+        $this->resource = $result;
902
+        return is_resource($result);
903
+    }
904
+
905
+
906
+    /**
907
+     * @param int $width
908
+     * @param int $height
909
+     * @return resource | bool
910
+     */
911
+    public function preciseResizeNew(int $width, int $height) {
912
+        if (!$this->valid()) {
913
+            $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
914
+            return false;
915
+        }
916
+        $widthOrig = imagesx($this->resource);
917
+        $heightOrig = imagesy($this->resource);
918
+        $process = imagecreatetruecolor($width, $height);
919
+        if ($process === false) {
920
+            $this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
921
+            return false;
922
+        }
923
+
924
+        // preserve transparency
925
+        if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
926
+            imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
927
+            imagealphablending($process, false);
928
+            imagesavealpha($process, true);
929
+        }
930
+
931
+        $res = imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
932
+        if ($res === false) {
933
+            $this->logger->error(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']);
934
+            imagedestroy($process);
935
+            return false;
936
+        }
937
+        return $process;
938
+    }
939
+
940
+    /**
941
+     * Crops the image to the middle square. If the image is already square it just returns.
942
+     *
943
+     * @param int $size maximum size for the result (optional)
944
+     * @return bool for success or failure
945
+     */
946
+    public function centerCrop($size = 0) {
947
+        if (!$this->valid()) {
948
+            $this->logger->error('OC_Image->centerCrop, No image loaded', ['app' => 'core']);
949
+            return false;
950
+        }
951
+        $widthOrig = imagesx($this->resource);
952
+        $heightOrig = imagesy($this->resource);
953
+        if ($widthOrig === $heightOrig and $size == 0) {
954
+            return true;
955
+        }
956
+        $ratioOrig = $widthOrig / $heightOrig;
957
+        $width = $height = min($widthOrig, $heightOrig);
958
+
959
+        if ($ratioOrig > 1) {
960
+            $x = ($widthOrig / 2) - ($width / 2);
961
+            $y = 0;
962
+        } else {
963
+            $y = ($heightOrig / 2) - ($height / 2);
964
+            $x = 0;
965
+        }
966
+        if ($size > 0) {
967
+            $targetWidth = $size;
968
+            $targetHeight = $size;
969
+        } else {
970
+            $targetWidth = $width;
971
+            $targetHeight = $height;
972
+        }
973
+        $process = imagecreatetruecolor($targetWidth, $targetHeight);
974
+        if ($process == false) {
975
+            $this->logger->error('OC_Image->centerCrop, Error creating true color image', ['app' => 'core']);
976
+            imagedestroy($process);
977
+            return false;
978
+        }
979
+
980
+        // preserve transparency
981
+        if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
982
+            imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
983
+            imagealphablending($process, false);
984
+            imagesavealpha($process, true);
985
+        }
986
+
987
+        imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
988
+        if ($process == false) {
989
+            $this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']);
990
+            imagedestroy($process);
991
+            return false;
992
+        }
993
+        imagedestroy($this->resource);
994
+        $this->resource = $process;
995
+        return true;
996
+    }
997
+
998
+    /**
999
+     * Crops the image from point $x$y with dimension $wx$h.
1000
+     *
1001
+     * @param int $x Horizontal position
1002
+     * @param int $y Vertical position
1003
+     * @param int $w Width
1004
+     * @param int $h Height
1005
+     * @return bool for success or failure
1006
+     */
1007
+    public function crop(int $x, int $y, int $w, int $h): bool {
1008
+        $result = $this->cropNew($x, $y, $w, $h);
1009
+        imagedestroy($this->resource);
1010
+        $this->resource = $result;
1011
+        return is_resource($result);
1012
+    }
1013
+
1014
+    /**
1015
+     * Crops the image from point $x$y with dimension $wx$h.
1016
+     *
1017
+     * @param int $x Horizontal position
1018
+     * @param int $y Vertical position
1019
+     * @param int $w Width
1020
+     * @param int $h Height
1021
+     * @return resource | bool
1022
+     */
1023
+    public function cropNew(int $x, int $y, int $w, int $h) {
1024
+        if (!$this->valid()) {
1025
+            $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1026
+            return false;
1027
+        }
1028
+        $process = imagecreatetruecolor($w, $h);
1029
+        if ($process == false) {
1030
+            $this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
1031
+            imagedestroy($process);
1032
+            return false;
1033
+        }
1034
+
1035
+        // preserve transparency
1036
+        if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1037
+            imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1038
+            imagealphablending($process, false);
1039
+            imagesavealpha($process, true);
1040
+        }
1041
+
1042
+        imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
1043
+        if ($process == false) {
1044
+            $this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, ['app' => 'core']);
1045
+            imagedestroy($process);
1046
+            return false;
1047
+        }
1048
+        return $process;
1049
+    }
1050
+
1051
+    /**
1052
+     * Resizes the image to fit within a boundary while preserving ratio.
1053
+     *
1054
+     * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
1055
+     *
1056
+     * @param integer $maxWidth
1057
+     * @param integer $maxHeight
1058
+     * @return bool
1059
+     */
1060
+    public function fitIn($maxWidth, $maxHeight) {
1061
+        if (!$this->valid()) {
1062
+            $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1063
+            return false;
1064
+        }
1065
+        $widthOrig = imagesx($this->resource);
1066
+        $heightOrig = imagesy($this->resource);
1067
+        $ratio = $widthOrig / $heightOrig;
1068
+
1069
+        $newWidth = min($maxWidth, $ratio * $maxHeight);
1070
+        $newHeight = min($maxHeight, $maxWidth / $ratio);
1071
+
1072
+        $this->preciseResize((int)round($newWidth), (int)round($newHeight));
1073
+        return true;
1074
+    }
1075
+
1076
+    /**
1077
+     * Shrinks larger images to fit within specified boundaries while preserving ratio.
1078
+     *
1079
+     * @param integer $maxWidth
1080
+     * @param integer $maxHeight
1081
+     * @return bool
1082
+     */
1083
+    public function scaleDownToFit($maxWidth, $maxHeight) {
1084
+        if (!$this->valid()) {
1085
+            $this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1086
+            return false;
1087
+        }
1088
+        $widthOrig = imagesx($this->resource);
1089
+        $heightOrig = imagesy($this->resource);
1090
+
1091
+        if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
1092
+            return $this->fitIn($maxWidth, $maxHeight);
1093
+        }
1094
+
1095
+        return false;
1096
+    }
1097
+
1098
+    public function copy(): IImage {
1099
+        $image = new OC_Image(null, $this->logger, $this->config);
1100
+        $image->resource = imagecreatetruecolor($this->width(), $this->height());
1101
+        imagecopy(
1102
+            $image->resource(),
1103
+            $this->resource(),
1104
+            0,
1105
+            0,
1106
+            0,
1107
+            0,
1108
+            $this->width(),
1109
+            $this->height()
1110
+        );
1111
+
1112
+        return $image;
1113
+    }
1114
+
1115
+    public function cropCopy(int $x, int $y, int $w, int $h): IImage {
1116
+        $image = new OC_Image(null, $this->logger, $this->config);
1117
+        $image->imageType = $this->imageType;
1118
+        $image->mimeType = $this->mimeType;
1119
+        $image->bitDepth = $this->bitDepth;
1120
+        $image->resource = $this->cropNew($x, $y, $w, $h);
1121
+
1122
+        return $image;
1123
+    }
1124
+
1125
+    public function preciseResizeCopy(int $width, int $height): IImage {
1126
+        $image = new OC_Image(null, $this->logger, $this->config);
1127
+        $image->imageType = $this->imageType;
1128
+        $image->mimeType = $this->mimeType;
1129
+        $image->bitDepth = $this->bitDepth;
1130
+        $image->resource = $this->preciseResizeNew($width, $height);
1131
+
1132
+        return $image;
1133
+    }
1134
+
1135
+    public function resizeCopy(int $maxSize): IImage {
1136
+        $image = new OC_Image(null, $this->logger, $this->config);
1137
+        $image->imageType = $this->imageType;
1138
+        $image->mimeType = $this->mimeType;
1139
+        $image->bitDepth = $this->bitDepth;
1140
+        $image->resource = $this->resizeNew($maxSize);
1141
+
1142
+        return $image;
1143
+    }
1144
+
1145
+
1146
+    /**
1147
+     * Resizes the image preserving ratio, returning a new copy
1148
+     *
1149
+     * @param integer $maxSize The maximum size of either the width or height.
1150
+     * @return bool
1151
+     */
1152
+    public function copyResize($maxSize): IImage {
1153
+    }
1154
+
1155
+    /**
1156
+     * Destroys the current image and resets the object
1157
+     */
1158
+    public function destroy() {
1159
+        if ($this->valid()) {
1160
+            imagedestroy($this->resource);
1161
+        }
1162
+        $this->resource = null;
1163
+    }
1164
+
1165
+    public function __destruct() {
1166
+        $this->destroy();
1167
+    }
1168 1168
 }
1169 1169
 
1170 1170
 if (!function_exists('imagebmp')) {
1171
-	/**
1172
-	 * Output a BMP image to either the browser or a file
1173
-	 *
1174
-	 * @link http://www.ugia.cn/wp-data/imagebmp.php
1175
-	 * @author legend <[email protected]>
1176
-	 * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
1177
-	 * @author mgutt <[email protected]>
1178
-	 * @version 1.00
1179
-	 * @param resource $im
1180
-	 * @param string $fileName [optional] <p>The path to save the file to.</p>
1181
-	 * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
1182
-	 * @param int $compression [optional]
1183
-	 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
1184
-	 */
1185
-	function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
1186
-		if (!in_array($bit, [1, 4, 8, 16, 24, 32])) {
1187
-			$bit = 24;
1188
-		} elseif ($bit == 32) {
1189
-			$bit = 24;
1190
-		}
1191
-		$bits = (int)pow(2, $bit);
1192
-		imagetruecolortopalette($im, true, $bits);
1193
-		$width = imagesx($im);
1194
-		$height = imagesy($im);
1195
-		$colorsNum = imagecolorstotal($im);
1196
-		$rgbQuad = '';
1197
-		if ($bit <= 8) {
1198
-			for ($i = 0; $i < $colorsNum; $i++) {
1199
-				$colors = imagecolorsforindex($im, $i);
1200
-				$rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
1201
-			}
1202
-			$bmpData = '';
1203
-			if ($compression == 0 || $bit < 8) {
1204
-				$compression = 0;
1205
-				$extra = '';
1206
-				$padding = 4 - ceil($width / (8 / $bit)) % 4;
1207
-				if ($padding % 4 != 0) {
1208
-					$extra = str_repeat("\0", $padding);
1209
-				}
1210
-				for ($j = $height - 1; $j >= 0; $j--) {
1211
-					$i = 0;
1212
-					while ($i < $width) {
1213
-						$bin = 0;
1214
-						$limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
1215
-						for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
1216
-							$index = imagecolorat($im, $i, $j);
1217
-							$bin |= $index << $k;
1218
-							$i++;
1219
-						}
1220
-						$bmpData .= chr($bin);
1221
-					}
1222
-					$bmpData .= $extra;
1223
-				}
1224
-			} // RLE8
1225
-			elseif ($compression == 1 && $bit == 8) {
1226
-				for ($j = $height - 1; $j >= 0; $j--) {
1227
-					$lastIndex = null;
1228
-					$sameNum = 0;
1229
-					for ($i = 0; $i <= $width; $i++) {
1230
-						$index = imagecolorat($im, $i, $j);
1231
-						if ($index !== $lastIndex || $sameNum > 255) {
1232
-							if ($sameNum != 0) {
1233
-								$bmpData .= chr($sameNum) . chr($lastIndex);
1234
-							}
1235
-							$lastIndex = $index;
1236
-							$sameNum = 1;
1237
-						} else {
1238
-							$sameNum++;
1239
-						}
1240
-					}
1241
-					$bmpData .= "\0\0";
1242
-				}
1243
-				$bmpData .= "\0\1";
1244
-			}
1245
-			$sizeQuad = strlen($rgbQuad);
1246
-			$sizeData = strlen($bmpData);
1247
-		} else {
1248
-			$extra = '';
1249
-			$padding = 4 - ($width * ($bit / 8)) % 4;
1250
-			if ($padding % 4 != 0) {
1251
-				$extra = str_repeat("\0", $padding);
1252
-			}
1253
-			$bmpData = '';
1254
-			for ($j = $height - 1; $j >= 0; $j--) {
1255
-				for ($i = 0; $i < $width; $i++) {
1256
-					$index = imagecolorat($im, $i, $j);
1257
-					$colors = imagecolorsforindex($im, $index);
1258
-					if ($bit == 16) {
1259
-						$bin = 0 << $bit;
1260
-						$bin |= ($colors['red'] >> 3) << 10;
1261
-						$bin |= ($colors['green'] >> 3) << 5;
1262
-						$bin |= $colors['blue'] >> 3;
1263
-						$bmpData .= pack("v", $bin);
1264
-					} else {
1265
-						$bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
1266
-					}
1267
-				}
1268
-				$bmpData .= $extra;
1269
-			}
1270
-			$sizeQuad = 0;
1271
-			$sizeData = strlen($bmpData);
1272
-			$colorsNum = 0;
1273
-		}
1274
-		$fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
1275
-		$infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
1276
-		if ($fileName != '') {
1277
-			$fp = fopen($fileName, 'wb');
1278
-			fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
1279
-			fclose($fp);
1280
-			return true;
1281
-		}
1282
-		echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
1283
-		return true;
1284
-	}
1171
+    /**
1172
+     * Output a BMP image to either the browser or a file
1173
+     *
1174
+     * @link http://www.ugia.cn/wp-data/imagebmp.php
1175
+     * @author legend <[email protected]>
1176
+     * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
1177
+     * @author mgutt <[email protected]>
1178
+     * @version 1.00
1179
+     * @param resource $im
1180
+     * @param string $fileName [optional] <p>The path to save the file to.</p>
1181
+     * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
1182
+     * @param int $compression [optional]
1183
+     * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
1184
+     */
1185
+    function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
1186
+        if (!in_array($bit, [1, 4, 8, 16, 24, 32])) {
1187
+            $bit = 24;
1188
+        } elseif ($bit == 32) {
1189
+            $bit = 24;
1190
+        }
1191
+        $bits = (int)pow(2, $bit);
1192
+        imagetruecolortopalette($im, true, $bits);
1193
+        $width = imagesx($im);
1194
+        $height = imagesy($im);
1195
+        $colorsNum = imagecolorstotal($im);
1196
+        $rgbQuad = '';
1197
+        if ($bit <= 8) {
1198
+            for ($i = 0; $i < $colorsNum; $i++) {
1199
+                $colors = imagecolorsforindex($im, $i);
1200
+                $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
1201
+            }
1202
+            $bmpData = '';
1203
+            if ($compression == 0 || $bit < 8) {
1204
+                $compression = 0;
1205
+                $extra = '';
1206
+                $padding = 4 - ceil($width / (8 / $bit)) % 4;
1207
+                if ($padding % 4 != 0) {
1208
+                    $extra = str_repeat("\0", $padding);
1209
+                }
1210
+                for ($j = $height - 1; $j >= 0; $j--) {
1211
+                    $i = 0;
1212
+                    while ($i < $width) {
1213
+                        $bin = 0;
1214
+                        $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
1215
+                        for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
1216
+                            $index = imagecolorat($im, $i, $j);
1217
+                            $bin |= $index << $k;
1218
+                            $i++;
1219
+                        }
1220
+                        $bmpData .= chr($bin);
1221
+                    }
1222
+                    $bmpData .= $extra;
1223
+                }
1224
+            } // RLE8
1225
+            elseif ($compression == 1 && $bit == 8) {
1226
+                for ($j = $height - 1; $j >= 0; $j--) {
1227
+                    $lastIndex = null;
1228
+                    $sameNum = 0;
1229
+                    for ($i = 0; $i <= $width; $i++) {
1230
+                        $index = imagecolorat($im, $i, $j);
1231
+                        if ($index !== $lastIndex || $sameNum > 255) {
1232
+                            if ($sameNum != 0) {
1233
+                                $bmpData .= chr($sameNum) . chr($lastIndex);
1234
+                            }
1235
+                            $lastIndex = $index;
1236
+                            $sameNum = 1;
1237
+                        } else {
1238
+                            $sameNum++;
1239
+                        }
1240
+                    }
1241
+                    $bmpData .= "\0\0";
1242
+                }
1243
+                $bmpData .= "\0\1";
1244
+            }
1245
+            $sizeQuad = strlen($rgbQuad);
1246
+            $sizeData = strlen($bmpData);
1247
+        } else {
1248
+            $extra = '';
1249
+            $padding = 4 - ($width * ($bit / 8)) % 4;
1250
+            if ($padding % 4 != 0) {
1251
+                $extra = str_repeat("\0", $padding);
1252
+            }
1253
+            $bmpData = '';
1254
+            for ($j = $height - 1; $j >= 0; $j--) {
1255
+                for ($i = 0; $i < $width; $i++) {
1256
+                    $index = imagecolorat($im, $i, $j);
1257
+                    $colors = imagecolorsforindex($im, $index);
1258
+                    if ($bit == 16) {
1259
+                        $bin = 0 << $bit;
1260
+                        $bin |= ($colors['red'] >> 3) << 10;
1261
+                        $bin |= ($colors['green'] >> 3) << 5;
1262
+                        $bin |= $colors['blue'] >> 3;
1263
+                        $bmpData .= pack("v", $bin);
1264
+                    } else {
1265
+                        $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
1266
+                    }
1267
+                }
1268
+                $bmpData .= $extra;
1269
+            }
1270
+            $sizeQuad = 0;
1271
+            $sizeData = strlen($bmpData);
1272
+            $colorsNum = 0;
1273
+        }
1274
+        $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
1275
+        $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
1276
+        if ($fileName != '') {
1277
+            $fp = fopen($fileName, 'wb');
1278
+            fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
1279
+            fclose($fp);
1280
+            return true;
1281
+        }
1282
+        echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
1283
+        return true;
1284
+    }
1285 1285
 }
1286 1286
 
1287 1287
 if (!function_exists('exif_imagetype')) {
1288
-	/**
1289
-	 * Workaround if exif_imagetype does not exist
1290
-	 *
1291
-	 * @link https://www.php.net/manual/en/function.exif-imagetype.php#80383
1292
-	 * @param string $fileName
1293
-	 * @return string|boolean
1294
-	 */
1295
-	function exif_imagetype($fileName) {
1296
-		if (($info = getimagesize($fileName)) !== false) {
1297
-			return $info[2];
1298
-		}
1299
-		return false;
1300
-	}
1288
+    /**
1289
+     * Workaround if exif_imagetype does not exist
1290
+     *
1291
+     * @link https://www.php.net/manual/en/function.exif-imagetype.php#80383
1292
+     * @param string $fileName
1293
+     * @return string|boolean
1294
+     */
1295
+    function exif_imagetype($fileName) {
1296
+        if (($info = getimagesize($fileName)) !== false) {
1297
+            return $info[2];
1298
+        }
1299
+        return false;
1300
+    }
1301 1301
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_Helper.php 1 patch
Indentation   +559 added lines, -559 removed lines patch added patch discarded remove patch
@@ -50,563 +50,563 @@
 block discarded – undo
50 50
  * Collection of useful functions
51 51
  */
52 52
 class OC_Helper {
53
-	private static $templateManager;
54
-
55
-	/**
56
-	 * Make a human file size
57
-	 * @param int $bytes file size in bytes
58
-	 * @return string a human readable file size
59
-	 *
60
-	 * Makes 2048 to 2 kB.
61
-	 */
62
-	public static function humanFileSize($bytes) {
63
-		if ($bytes < 0) {
64
-			return "?";
65
-		}
66
-		if ($bytes < 1024) {
67
-			return "$bytes B";
68
-		}
69
-		$bytes = round($bytes / 1024, 0);
70
-		if ($bytes < 1024) {
71
-			return "$bytes KB";
72
-		}
73
-		$bytes = round($bytes / 1024, 1);
74
-		if ($bytes < 1024) {
75
-			return "$bytes MB";
76
-		}
77
-		$bytes = round($bytes / 1024, 1);
78
-		if ($bytes < 1024) {
79
-			return "$bytes GB";
80
-		}
81
-		$bytes = round($bytes / 1024, 1);
82
-		if ($bytes < 1024) {
83
-			return "$bytes TB";
84
-		}
85
-
86
-		$bytes = round($bytes / 1024, 1);
87
-		return "$bytes PB";
88
-	}
89
-
90
-	/**
91
-	 * Make a computer file size
92
-	 * @param string $str file size in human readable format
93
-	 * @return float|bool a file size in bytes
94
-	 *
95
-	 * Makes 2kB to 2048.
96
-	 *
97
-	 * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
98
-	 */
99
-	public static function computerFileSize($str) {
100
-		$str = strtolower($str);
101
-		if (is_numeric($str)) {
102
-			return (float)$str;
103
-		}
104
-
105
-		$bytes_array = [
106
-			'b' => 1,
107
-			'k' => 1024,
108
-			'kb' => 1024,
109
-			'mb' => 1024 * 1024,
110
-			'm' => 1024 * 1024,
111
-			'gb' => 1024 * 1024 * 1024,
112
-			'g' => 1024 * 1024 * 1024,
113
-			'tb' => 1024 * 1024 * 1024 * 1024,
114
-			't' => 1024 * 1024 * 1024 * 1024,
115
-			'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
116
-			'p' => 1024 * 1024 * 1024 * 1024 * 1024,
117
-		];
118
-
119
-		$bytes = (float)$str;
120
-
121
-		if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) {
122
-			$bytes *= $bytes_array[$matches[1]];
123
-		} else {
124
-			return false;
125
-		}
126
-
127
-		$bytes = round($bytes);
128
-
129
-		return $bytes;
130
-	}
131
-
132
-	/**
133
-	 * Recursive copying of folders
134
-	 * @param string $src source folder
135
-	 * @param string $dest target folder
136
-	 *
137
-	 */
138
-	public static function copyr($src, $dest) {
139
-		if (is_dir($src)) {
140
-			if (!is_dir($dest)) {
141
-				mkdir($dest);
142
-			}
143
-			$files = scandir($src);
144
-			foreach ($files as $file) {
145
-				if ($file != "." && $file != "..") {
146
-					self::copyr("$src/$file", "$dest/$file");
147
-				}
148
-			}
149
-		} elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) {
150
-			copy($src, $dest);
151
-		}
152
-	}
153
-
154
-	/**
155
-	 * Recursive deletion of folders
156
-	 * @param string $dir path to the folder
157
-	 * @param bool $deleteSelf if set to false only the content of the folder will be deleted
158
-	 * @return bool
159
-	 */
160
-	public static function rmdirr($dir, $deleteSelf = true) {
161
-		if (is_dir($dir)) {
162
-			$files = new RecursiveIteratorIterator(
163
-				new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
164
-				RecursiveIteratorIterator::CHILD_FIRST
165
-			);
166
-
167
-			foreach ($files as $fileInfo) {
168
-				/** @var SplFileInfo $fileInfo */
169
-				if ($fileInfo->isLink()) {
170
-					unlink($fileInfo->getPathname());
171
-				} elseif ($fileInfo->isDir()) {
172
-					rmdir($fileInfo->getRealPath());
173
-				} else {
174
-					unlink($fileInfo->getRealPath());
175
-				}
176
-			}
177
-			if ($deleteSelf) {
178
-				rmdir($dir);
179
-			}
180
-		} elseif (file_exists($dir)) {
181
-			if ($deleteSelf) {
182
-				unlink($dir);
183
-			}
184
-		}
185
-		if (!$deleteSelf) {
186
-			return true;
187
-		}
188
-
189
-		return !file_exists($dir);
190
-	}
191
-
192
-	/**
193
-	 * @deprecated 18.0.0
194
-	 * @return \OC\Files\Type\TemplateManager
195
-	 */
196
-	public static function getFileTemplateManager() {
197
-		if (!self::$templateManager) {
198
-			self::$templateManager = new \OC\Files\Type\TemplateManager();
199
-		}
200
-		return self::$templateManager;
201
-	}
202
-
203
-	/**
204
-	 * detect if a given program is found in the search PATH
205
-	 *
206
-	 * @param string $name
207
-	 * @param bool $path
208
-	 * @internal param string $program name
209
-	 * @internal param string $optional search path, defaults to $PATH
210
-	 * @return bool    true if executable program found in path
211
-	 */
212
-	public static function canExecute($name, $path = false) {
213
-		// path defaults to PATH from environment if not set
214
-		if ($path === false) {
215
-			$path = getenv("PATH");
216
-		}
217
-		// we look for an executable file of that name
218
-		$exts = [""];
219
-		$check_fn = "is_executable";
220
-		// Default check will be done with $path directories :
221
-		$dirs = explode(PATH_SEPARATOR, $path);
222
-		// WARNING : We have to check if open_basedir is enabled :
223
-		$obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
224
-		if ($obd != "none") {
225
-			$obd_values = explode(PATH_SEPARATOR, $obd);
226
-			if (count($obd_values) > 0 and $obd_values[0]) {
227
-				// open_basedir is in effect !
228
-				// We need to check if the program is in one of these dirs :
229
-				$dirs = $obd_values;
230
-			}
231
-		}
232
-		foreach ($dirs as $dir) {
233
-			foreach ($exts as $ext) {
234
-				if ($check_fn("$dir/$name" . $ext)) {
235
-					return true;
236
-				}
237
-			}
238
-		}
239
-		return false;
240
-	}
241
-
242
-	/**
243
-	 * copy the contents of one stream to another
244
-	 *
245
-	 * @param resource $source
246
-	 * @param resource $target
247
-	 * @return array the number of bytes copied and result
248
-	 */
249
-	public static function streamCopy($source, $target) {
250
-		if (!$source or !$target) {
251
-			return [0, false];
252
-		}
253
-		$bufSize = 8192;
254
-		$result = true;
255
-		$count = 0;
256
-		while (!feof($source)) {
257
-			$buf = fread($source, $bufSize);
258
-			$bytesWritten = fwrite($target, $buf);
259
-			if ($bytesWritten !== false) {
260
-				$count += $bytesWritten;
261
-			}
262
-			// note: strlen is expensive so only use it when necessary,
263
-			// on the last block
264
-			if ($bytesWritten === false
265
-				|| ($bytesWritten < $bufSize && $bytesWritten < strlen($buf))
266
-			) {
267
-				// write error, could be disk full ?
268
-				$result = false;
269
-				break;
270
-			}
271
-		}
272
-		return [$count, $result];
273
-	}
274
-
275
-	/**
276
-	 * Adds a suffix to the name in case the file exists
277
-	 *
278
-	 * @param string $path
279
-	 * @param string $filename
280
-	 * @return string
281
-	 */
282
-	public static function buildNotExistingFileName($path, $filename) {
283
-		$view = \OC\Files\Filesystem::getView();
284
-		return self::buildNotExistingFileNameForView($path, $filename, $view);
285
-	}
286
-
287
-	/**
288
-	 * Adds a suffix to the name in case the file exists
289
-	 *
290
-	 * @param string $path
291
-	 * @param string $filename
292
-	 * @return string
293
-	 */
294
-	public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
295
-		if ($path === '/') {
296
-			$path = '';
297
-		}
298
-		if ($pos = strrpos($filename, '.')) {
299
-			$name = substr($filename, 0, $pos);
300
-			$ext = substr($filename, $pos);
301
-		} else {
302
-			$name = $filename;
303
-			$ext = '';
304
-		}
305
-
306
-		$newpath = $path . '/' . $filename;
307
-		if ($view->file_exists($newpath)) {
308
-			if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
309
-				//Replace the last "(number)" with "(number+1)"
310
-				$last_match = count($matches[0]) - 1;
311
-				$counter = $matches[1][$last_match][0] + 1;
312
-				$offset = $matches[0][$last_match][1];
313
-				$match_length = strlen($matches[0][$last_match][0]);
314
-			} else {
315
-				$counter = 2;
316
-				$match_length = 0;
317
-				$offset = false;
318
-			}
319
-			do {
320
-				if ($offset) {
321
-					//Replace the last "(number)" with "(number+1)"
322
-					$newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
323
-				} else {
324
-					$newname = $name . ' (' . $counter . ')';
325
-				}
326
-				$newpath = $path . '/' . $newname . $ext;
327
-				$counter++;
328
-			} while ($view->file_exists($newpath));
329
-		}
330
-
331
-		return $newpath;
332
-	}
333
-
334
-	/**
335
-	 * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
336
-	 *
337
-	 * @param array $input The array to work on
338
-	 * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
339
-	 * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
340
-	 * @return array
341
-	 *
342
-	 * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
343
-	 * based on https://www.php.net/manual/en/function.array-change-key-case.php#107715
344
-	 *
345
-	 */
346
-	public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
347
-		$case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
348
-		$ret = [];
349
-		foreach ($input as $k => $v) {
350
-			$ret[mb_convert_case($k, $case, $encoding)] = $v;
351
-		}
352
-		return $ret;
353
-	}
354
-
355
-	/**
356
-	 * performs a search in a nested array
357
-	 * @param array $haystack the array to be searched
358
-	 * @param string $needle the search string
359
-	 * @param mixed $index optional, only search this key name
360
-	 * @return mixed the key of the matching field, otherwise false
361
-	 *
362
-	 * performs a search in a nested array
363
-	 *
364
-	 * taken from https://www.php.net/manual/en/function.array-search.php#97645
365
-	 */
366
-	public static function recursiveArraySearch($haystack, $needle, $index = null) {
367
-		$aIt = new RecursiveArrayIterator($haystack);
368
-		$it = new RecursiveIteratorIterator($aIt);
369
-
370
-		while ($it->valid()) {
371
-			if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) {
372
-				return $aIt->key();
373
-			}
374
-
375
-			$it->next();
376
-		}
377
-
378
-		return false;
379
-	}
380
-
381
-	/**
382
-	 * calculates the maximum upload size respecting system settings, free space and user quota
383
-	 *
384
-	 * @param string $dir the current folder where the user currently operates
385
-	 * @param int $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
386
-	 * @return int number of bytes representing
387
-	 */
388
-	public static function maxUploadFilesize($dir, $freeSpace = null) {
389
-		if (is_null($freeSpace) || $freeSpace < 0) {
390
-			$freeSpace = self::freeSpace($dir);
391
-		}
392
-		return min($freeSpace, self::uploadLimit());
393
-	}
394
-
395
-	/**
396
-	 * Calculate free space left within user quota
397
-	 *
398
-	 * @param string $dir the current folder where the user currently operates
399
-	 * @return int number of bytes representing
400
-	 */
401
-	public static function freeSpace($dir) {
402
-		$freeSpace = \OC\Files\Filesystem::free_space($dir);
403
-		if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
404
-			$freeSpace = max($freeSpace, 0);
405
-			return $freeSpace;
406
-		} else {
407
-			return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
408
-		}
409
-	}
410
-
411
-	/**
412
-	 * Calculate PHP upload limit
413
-	 *
414
-	 * @return int PHP upload file size limit
415
-	 */
416
-	public static function uploadLimit() {
417
-		$ini = \OC::$server->get(IniGetWrapper::class);
418
-		$upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize'));
419
-		$post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size'));
420
-		if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) {
421
-			return INF;
422
-		} elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) {
423
-			return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
424
-		} else {
425
-			return min($upload_max_filesize, $post_max_size);
426
-		}
427
-	}
428
-
429
-	/**
430
-	 * Checks if a function is available
431
-	 *
432
-	 * @param string $function_name
433
-	 * @return bool
434
-	 */
435
-	public static function is_function_enabled($function_name) {
436
-		if (!function_exists($function_name)) {
437
-			return false;
438
-		}
439
-		$ini = \OC::$server->get(IniGetWrapper::class);
440
-		$disabled = explode(',', $ini->get('disable_functions') ?: '');
441
-		$disabled = array_map('trim', $disabled);
442
-		if (in_array($function_name, $disabled)) {
443
-			return false;
444
-		}
445
-		$disabled = explode(',', $ini->get('suhosin.executor.func.blacklist') ?: '');
446
-		$disabled = array_map('trim', $disabled);
447
-		if (in_array($function_name, $disabled)) {
448
-			return false;
449
-		}
450
-		return true;
451
-	}
452
-
453
-	/**
454
-	 * Try to find a program
455
-	 *
456
-	 * @param string $program
457
-	 * @return null|string
458
-	 */
459
-	public static function findBinaryPath($program) {
460
-		$memcache = \OC::$server->getMemCacheFactory()->createDistributed('findBinaryPath');
461
-		if ($memcache->hasKey($program)) {
462
-			return $memcache->get($program);
463
-		}
464
-		$result = null;
465
-		if (self::is_function_enabled('exec')) {
466
-			$exeSniffer = new ExecutableFinder();
467
-			// Returns null if nothing is found
468
-			$result = $exeSniffer->find($program, null, ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/opt/bin']);
469
-		}
470
-		// store the value for 5 minutes
471
-		$memcache->set($program, $result, 300);
472
-		return $result;
473
-	}
474
-
475
-	/**
476
-	 * Calculate the disc space for the given path
477
-	 *
478
-	 * BEWARE: this requires that Util::setupFS() was called
479
-	 * already !
480
-	 *
481
-	 * @param string $path
482
-	 * @param \OCP\Files\FileInfo $rootInfo (optional)
483
-	 * @return array
484
-	 * @throws \OCP\Files\NotFoundException
485
-	 */
486
-	public static function getStorageInfo($path, $rootInfo = null) {
487
-		// return storage info without adding mount points
488
-		$includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
489
-
490
-		if (!$rootInfo) {
491
-			$rootInfo = \OC\Files\Filesystem::getFileInfo($path, $includeExtStorage ? 'ext' : false);
492
-		}
493
-		if (!$rootInfo instanceof \OCP\Files\FileInfo) {
494
-			throw new \OCP\Files\NotFoundException();
495
-		}
496
-		$used = $rootInfo->getSize();
497
-		if ($used < 0) {
498
-			$used = 0;
499
-		}
500
-		$quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
501
-		$mount = $rootInfo->getMountPoint();
502
-		$storage = $mount->getStorage();
503
-		$sourceStorage = $storage;
504
-		if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
505
-			$includeExtStorage = false;
506
-			$sourceStorage = $storage->getSourceStorage();
507
-		}
508
-		if ($includeExtStorage) {
509
-			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
510
-				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
511
-			) {
512
-				/** @var \OC\Files\Storage\Home $storage */
513
-				$user = $storage->getUser();
514
-			} else {
515
-				$user = \OC::$server->getUserSession()->getUser();
516
-			}
517
-			$quota = OC_Util::getUserQuota($user);
518
-			if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
519
-				// always get free space / total space from root + mount points
520
-				return self::getGlobalStorageInfo($quota);
521
-			}
522
-		}
523
-
524
-		// TODO: need a better way to get total space from storage
525
-		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
526
-			/** @var \OC\Files\Storage\Wrapper\Quota $storage */
527
-			$quota = $sourceStorage->getQuota();
528
-		}
529
-		$free = $sourceStorage->free_space($rootInfo->getInternalPath());
530
-		if ($free >= 0) {
531
-			$total = $free + $used;
532
-		} else {
533
-			$total = $free; //either unknown or unlimited
534
-		}
535
-		if ($total > 0) {
536
-			if ($quota > 0 && $total > $quota) {
537
-				$total = $quota;
538
-			}
539
-			// prevent division by zero or error codes (negative values)
540
-			$relative = round(($used / $total) * 10000) / 100;
541
-		} else {
542
-			$relative = 0;
543
-		}
544
-
545
-		$ownerId = $storage->getOwner($path);
546
-		$ownerDisplayName = '';
547
-		$owner = \OC::$server->getUserManager()->get($ownerId);
548
-		if ($owner) {
549
-			$ownerDisplayName = $owner->getDisplayName();
550
-		}
551
-		if (substr_count($mount->getMountPoint(), '/') < 3) {
552
-			$mountPoint = '';
553
-		} else {
554
-			[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
555
-		}
556
-
557
-		return [
558
-			'free' => $free,
559
-			'used' => $used,
560
-			'quota' => $quota,
561
-			'total' => $total,
562
-			'relative' => $relative,
563
-			'owner' => $ownerId,
564
-			'ownerDisplayName' => $ownerDisplayName,
565
-			'mountType' => $mount->getMountType(),
566
-			'mountPoint' => trim($mountPoint, '/'),
567
-		];
568
-	}
569
-
570
-	/**
571
-	 * Get storage info including all mount points and quota
572
-	 *
573
-	 * @param int $quota
574
-	 * @return array
575
-	 */
576
-	private static function getGlobalStorageInfo($quota) {
577
-		$rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
578
-		$used = $rootInfo['size'];
579
-		if ($used < 0) {
580
-			$used = 0;
581
-		}
582
-
583
-		$total = $quota;
584
-		$free = $quota - $used;
585
-
586
-		if ($total > 0) {
587
-			if ($quota > 0 && $total > $quota) {
588
-				$total = $quota;
589
-			}
590
-			// prevent division by zero or error codes (negative values)
591
-			$relative = round(($used / $total) * 10000) / 100;
592
-		} else {
593
-			$relative = 0;
594
-		}
595
-
596
-		return [
597
-			'free' => $free,
598
-			'used' => $used,
599
-			'total' => $total,
600
-			'relative' => $relative,
601
-			'quota' => $quota
602
-		];
603
-	}
604
-
605
-	/**
606
-	 * Returns whether the config file is set manually to read-only
607
-	 * @return bool
608
-	 */
609
-	public static function isReadOnlyConfigEnabled() {
610
-		return \OC::$server->getConfig()->getSystemValue('config_is_read_only', false);
611
-	}
53
+    private static $templateManager;
54
+
55
+    /**
56
+     * Make a human file size
57
+     * @param int $bytes file size in bytes
58
+     * @return string a human readable file size
59
+     *
60
+     * Makes 2048 to 2 kB.
61
+     */
62
+    public static function humanFileSize($bytes) {
63
+        if ($bytes < 0) {
64
+            return "?";
65
+        }
66
+        if ($bytes < 1024) {
67
+            return "$bytes B";
68
+        }
69
+        $bytes = round($bytes / 1024, 0);
70
+        if ($bytes < 1024) {
71
+            return "$bytes KB";
72
+        }
73
+        $bytes = round($bytes / 1024, 1);
74
+        if ($bytes < 1024) {
75
+            return "$bytes MB";
76
+        }
77
+        $bytes = round($bytes / 1024, 1);
78
+        if ($bytes < 1024) {
79
+            return "$bytes GB";
80
+        }
81
+        $bytes = round($bytes / 1024, 1);
82
+        if ($bytes < 1024) {
83
+            return "$bytes TB";
84
+        }
85
+
86
+        $bytes = round($bytes / 1024, 1);
87
+        return "$bytes PB";
88
+    }
89
+
90
+    /**
91
+     * Make a computer file size
92
+     * @param string $str file size in human readable format
93
+     * @return float|bool a file size in bytes
94
+     *
95
+     * Makes 2kB to 2048.
96
+     *
97
+     * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
98
+     */
99
+    public static function computerFileSize($str) {
100
+        $str = strtolower($str);
101
+        if (is_numeric($str)) {
102
+            return (float)$str;
103
+        }
104
+
105
+        $bytes_array = [
106
+            'b' => 1,
107
+            'k' => 1024,
108
+            'kb' => 1024,
109
+            'mb' => 1024 * 1024,
110
+            'm' => 1024 * 1024,
111
+            'gb' => 1024 * 1024 * 1024,
112
+            'g' => 1024 * 1024 * 1024,
113
+            'tb' => 1024 * 1024 * 1024 * 1024,
114
+            't' => 1024 * 1024 * 1024 * 1024,
115
+            'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
116
+            'p' => 1024 * 1024 * 1024 * 1024 * 1024,
117
+        ];
118
+
119
+        $bytes = (float)$str;
120
+
121
+        if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) {
122
+            $bytes *= $bytes_array[$matches[1]];
123
+        } else {
124
+            return false;
125
+        }
126
+
127
+        $bytes = round($bytes);
128
+
129
+        return $bytes;
130
+    }
131
+
132
+    /**
133
+     * Recursive copying of folders
134
+     * @param string $src source folder
135
+     * @param string $dest target folder
136
+     *
137
+     */
138
+    public static function copyr($src, $dest) {
139
+        if (is_dir($src)) {
140
+            if (!is_dir($dest)) {
141
+                mkdir($dest);
142
+            }
143
+            $files = scandir($src);
144
+            foreach ($files as $file) {
145
+                if ($file != "." && $file != "..") {
146
+                    self::copyr("$src/$file", "$dest/$file");
147
+                }
148
+            }
149
+        } elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) {
150
+            copy($src, $dest);
151
+        }
152
+    }
153
+
154
+    /**
155
+     * Recursive deletion of folders
156
+     * @param string $dir path to the folder
157
+     * @param bool $deleteSelf if set to false only the content of the folder will be deleted
158
+     * @return bool
159
+     */
160
+    public static function rmdirr($dir, $deleteSelf = true) {
161
+        if (is_dir($dir)) {
162
+            $files = new RecursiveIteratorIterator(
163
+                new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
164
+                RecursiveIteratorIterator::CHILD_FIRST
165
+            );
166
+
167
+            foreach ($files as $fileInfo) {
168
+                /** @var SplFileInfo $fileInfo */
169
+                if ($fileInfo->isLink()) {
170
+                    unlink($fileInfo->getPathname());
171
+                } elseif ($fileInfo->isDir()) {
172
+                    rmdir($fileInfo->getRealPath());
173
+                } else {
174
+                    unlink($fileInfo->getRealPath());
175
+                }
176
+            }
177
+            if ($deleteSelf) {
178
+                rmdir($dir);
179
+            }
180
+        } elseif (file_exists($dir)) {
181
+            if ($deleteSelf) {
182
+                unlink($dir);
183
+            }
184
+        }
185
+        if (!$deleteSelf) {
186
+            return true;
187
+        }
188
+
189
+        return !file_exists($dir);
190
+    }
191
+
192
+    /**
193
+     * @deprecated 18.0.0
194
+     * @return \OC\Files\Type\TemplateManager
195
+     */
196
+    public static function getFileTemplateManager() {
197
+        if (!self::$templateManager) {
198
+            self::$templateManager = new \OC\Files\Type\TemplateManager();
199
+        }
200
+        return self::$templateManager;
201
+    }
202
+
203
+    /**
204
+     * detect if a given program is found in the search PATH
205
+     *
206
+     * @param string $name
207
+     * @param bool $path
208
+     * @internal param string $program name
209
+     * @internal param string $optional search path, defaults to $PATH
210
+     * @return bool    true if executable program found in path
211
+     */
212
+    public static function canExecute($name, $path = false) {
213
+        // path defaults to PATH from environment if not set
214
+        if ($path === false) {
215
+            $path = getenv("PATH");
216
+        }
217
+        // we look for an executable file of that name
218
+        $exts = [""];
219
+        $check_fn = "is_executable";
220
+        // Default check will be done with $path directories :
221
+        $dirs = explode(PATH_SEPARATOR, $path);
222
+        // WARNING : We have to check if open_basedir is enabled :
223
+        $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
224
+        if ($obd != "none") {
225
+            $obd_values = explode(PATH_SEPARATOR, $obd);
226
+            if (count($obd_values) > 0 and $obd_values[0]) {
227
+                // open_basedir is in effect !
228
+                // We need to check if the program is in one of these dirs :
229
+                $dirs = $obd_values;
230
+            }
231
+        }
232
+        foreach ($dirs as $dir) {
233
+            foreach ($exts as $ext) {
234
+                if ($check_fn("$dir/$name" . $ext)) {
235
+                    return true;
236
+                }
237
+            }
238
+        }
239
+        return false;
240
+    }
241
+
242
+    /**
243
+     * copy the contents of one stream to another
244
+     *
245
+     * @param resource $source
246
+     * @param resource $target
247
+     * @return array the number of bytes copied and result
248
+     */
249
+    public static function streamCopy($source, $target) {
250
+        if (!$source or !$target) {
251
+            return [0, false];
252
+        }
253
+        $bufSize = 8192;
254
+        $result = true;
255
+        $count = 0;
256
+        while (!feof($source)) {
257
+            $buf = fread($source, $bufSize);
258
+            $bytesWritten = fwrite($target, $buf);
259
+            if ($bytesWritten !== false) {
260
+                $count += $bytesWritten;
261
+            }
262
+            // note: strlen is expensive so only use it when necessary,
263
+            // on the last block
264
+            if ($bytesWritten === false
265
+                || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf))
266
+            ) {
267
+                // write error, could be disk full ?
268
+                $result = false;
269
+                break;
270
+            }
271
+        }
272
+        return [$count, $result];
273
+    }
274
+
275
+    /**
276
+     * Adds a suffix to the name in case the file exists
277
+     *
278
+     * @param string $path
279
+     * @param string $filename
280
+     * @return string
281
+     */
282
+    public static function buildNotExistingFileName($path, $filename) {
283
+        $view = \OC\Files\Filesystem::getView();
284
+        return self::buildNotExistingFileNameForView($path, $filename, $view);
285
+    }
286
+
287
+    /**
288
+     * Adds a suffix to the name in case the file exists
289
+     *
290
+     * @param string $path
291
+     * @param string $filename
292
+     * @return string
293
+     */
294
+    public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
295
+        if ($path === '/') {
296
+            $path = '';
297
+        }
298
+        if ($pos = strrpos($filename, '.')) {
299
+            $name = substr($filename, 0, $pos);
300
+            $ext = substr($filename, $pos);
301
+        } else {
302
+            $name = $filename;
303
+            $ext = '';
304
+        }
305
+
306
+        $newpath = $path . '/' . $filename;
307
+        if ($view->file_exists($newpath)) {
308
+            if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
309
+                //Replace the last "(number)" with "(number+1)"
310
+                $last_match = count($matches[0]) - 1;
311
+                $counter = $matches[1][$last_match][0] + 1;
312
+                $offset = $matches[0][$last_match][1];
313
+                $match_length = strlen($matches[0][$last_match][0]);
314
+            } else {
315
+                $counter = 2;
316
+                $match_length = 0;
317
+                $offset = false;
318
+            }
319
+            do {
320
+                if ($offset) {
321
+                    //Replace the last "(number)" with "(number+1)"
322
+                    $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
323
+                } else {
324
+                    $newname = $name . ' (' . $counter . ')';
325
+                }
326
+                $newpath = $path . '/' . $newname . $ext;
327
+                $counter++;
328
+            } while ($view->file_exists($newpath));
329
+        }
330
+
331
+        return $newpath;
332
+    }
333
+
334
+    /**
335
+     * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
336
+     *
337
+     * @param array $input The array to work on
338
+     * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
339
+     * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
340
+     * @return array
341
+     *
342
+     * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
343
+     * based on https://www.php.net/manual/en/function.array-change-key-case.php#107715
344
+     *
345
+     */
346
+    public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
347
+        $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
348
+        $ret = [];
349
+        foreach ($input as $k => $v) {
350
+            $ret[mb_convert_case($k, $case, $encoding)] = $v;
351
+        }
352
+        return $ret;
353
+    }
354
+
355
+    /**
356
+     * performs a search in a nested array
357
+     * @param array $haystack the array to be searched
358
+     * @param string $needle the search string
359
+     * @param mixed $index optional, only search this key name
360
+     * @return mixed the key of the matching field, otherwise false
361
+     *
362
+     * performs a search in a nested array
363
+     *
364
+     * taken from https://www.php.net/manual/en/function.array-search.php#97645
365
+     */
366
+    public static function recursiveArraySearch($haystack, $needle, $index = null) {
367
+        $aIt = new RecursiveArrayIterator($haystack);
368
+        $it = new RecursiveIteratorIterator($aIt);
369
+
370
+        while ($it->valid()) {
371
+            if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) {
372
+                return $aIt->key();
373
+            }
374
+
375
+            $it->next();
376
+        }
377
+
378
+        return false;
379
+    }
380
+
381
+    /**
382
+     * calculates the maximum upload size respecting system settings, free space and user quota
383
+     *
384
+     * @param string $dir the current folder where the user currently operates
385
+     * @param int $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
386
+     * @return int number of bytes representing
387
+     */
388
+    public static function maxUploadFilesize($dir, $freeSpace = null) {
389
+        if (is_null($freeSpace) || $freeSpace < 0) {
390
+            $freeSpace = self::freeSpace($dir);
391
+        }
392
+        return min($freeSpace, self::uploadLimit());
393
+    }
394
+
395
+    /**
396
+     * Calculate free space left within user quota
397
+     *
398
+     * @param string $dir the current folder where the user currently operates
399
+     * @return int number of bytes representing
400
+     */
401
+    public static function freeSpace($dir) {
402
+        $freeSpace = \OC\Files\Filesystem::free_space($dir);
403
+        if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
404
+            $freeSpace = max($freeSpace, 0);
405
+            return $freeSpace;
406
+        } else {
407
+            return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
408
+        }
409
+    }
410
+
411
+    /**
412
+     * Calculate PHP upload limit
413
+     *
414
+     * @return int PHP upload file size limit
415
+     */
416
+    public static function uploadLimit() {
417
+        $ini = \OC::$server->get(IniGetWrapper::class);
418
+        $upload_max_filesize = OCP\Util::computerFileSize($ini->get('upload_max_filesize'));
419
+        $post_max_size = OCP\Util::computerFileSize($ini->get('post_max_size'));
420
+        if ((int)$upload_max_filesize === 0 and (int)$post_max_size === 0) {
421
+            return INF;
422
+        } elseif ((int)$upload_max_filesize === 0 or (int)$post_max_size === 0) {
423
+            return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
424
+        } else {
425
+            return min($upload_max_filesize, $post_max_size);
426
+        }
427
+    }
428
+
429
+    /**
430
+     * Checks if a function is available
431
+     *
432
+     * @param string $function_name
433
+     * @return bool
434
+     */
435
+    public static function is_function_enabled($function_name) {
436
+        if (!function_exists($function_name)) {
437
+            return false;
438
+        }
439
+        $ini = \OC::$server->get(IniGetWrapper::class);
440
+        $disabled = explode(',', $ini->get('disable_functions') ?: '');
441
+        $disabled = array_map('trim', $disabled);
442
+        if (in_array($function_name, $disabled)) {
443
+            return false;
444
+        }
445
+        $disabled = explode(',', $ini->get('suhosin.executor.func.blacklist') ?: '');
446
+        $disabled = array_map('trim', $disabled);
447
+        if (in_array($function_name, $disabled)) {
448
+            return false;
449
+        }
450
+        return true;
451
+    }
452
+
453
+    /**
454
+     * Try to find a program
455
+     *
456
+     * @param string $program
457
+     * @return null|string
458
+     */
459
+    public static function findBinaryPath($program) {
460
+        $memcache = \OC::$server->getMemCacheFactory()->createDistributed('findBinaryPath');
461
+        if ($memcache->hasKey($program)) {
462
+            return $memcache->get($program);
463
+        }
464
+        $result = null;
465
+        if (self::is_function_enabled('exec')) {
466
+            $exeSniffer = new ExecutableFinder();
467
+            // Returns null if nothing is found
468
+            $result = $exeSniffer->find($program, null, ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/opt/bin']);
469
+        }
470
+        // store the value for 5 minutes
471
+        $memcache->set($program, $result, 300);
472
+        return $result;
473
+    }
474
+
475
+    /**
476
+     * Calculate the disc space for the given path
477
+     *
478
+     * BEWARE: this requires that Util::setupFS() was called
479
+     * already !
480
+     *
481
+     * @param string $path
482
+     * @param \OCP\Files\FileInfo $rootInfo (optional)
483
+     * @return array
484
+     * @throws \OCP\Files\NotFoundException
485
+     */
486
+    public static function getStorageInfo($path, $rootInfo = null) {
487
+        // return storage info without adding mount points
488
+        $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
489
+
490
+        if (!$rootInfo) {
491
+            $rootInfo = \OC\Files\Filesystem::getFileInfo($path, $includeExtStorage ? 'ext' : false);
492
+        }
493
+        if (!$rootInfo instanceof \OCP\Files\FileInfo) {
494
+            throw new \OCP\Files\NotFoundException();
495
+        }
496
+        $used = $rootInfo->getSize();
497
+        if ($used < 0) {
498
+            $used = 0;
499
+        }
500
+        $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
501
+        $mount = $rootInfo->getMountPoint();
502
+        $storage = $mount->getStorage();
503
+        $sourceStorage = $storage;
504
+        if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
505
+            $includeExtStorage = false;
506
+            $sourceStorage = $storage->getSourceStorage();
507
+        }
508
+        if ($includeExtStorage) {
509
+            if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
510
+                || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
511
+            ) {
512
+                /** @var \OC\Files\Storage\Home $storage */
513
+                $user = $storage->getUser();
514
+            } else {
515
+                $user = \OC::$server->getUserSession()->getUser();
516
+            }
517
+            $quota = OC_Util::getUserQuota($user);
518
+            if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
519
+                // always get free space / total space from root + mount points
520
+                return self::getGlobalStorageInfo($quota);
521
+            }
522
+        }
523
+
524
+        // TODO: need a better way to get total space from storage
525
+        if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
526
+            /** @var \OC\Files\Storage\Wrapper\Quota $storage */
527
+            $quota = $sourceStorage->getQuota();
528
+        }
529
+        $free = $sourceStorage->free_space($rootInfo->getInternalPath());
530
+        if ($free >= 0) {
531
+            $total = $free + $used;
532
+        } else {
533
+            $total = $free; //either unknown or unlimited
534
+        }
535
+        if ($total > 0) {
536
+            if ($quota > 0 && $total > $quota) {
537
+                $total = $quota;
538
+            }
539
+            // prevent division by zero or error codes (negative values)
540
+            $relative = round(($used / $total) * 10000) / 100;
541
+        } else {
542
+            $relative = 0;
543
+        }
544
+
545
+        $ownerId = $storage->getOwner($path);
546
+        $ownerDisplayName = '';
547
+        $owner = \OC::$server->getUserManager()->get($ownerId);
548
+        if ($owner) {
549
+            $ownerDisplayName = $owner->getDisplayName();
550
+        }
551
+        if (substr_count($mount->getMountPoint(), '/') < 3) {
552
+            $mountPoint = '';
553
+        } else {
554
+            [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
555
+        }
556
+
557
+        return [
558
+            'free' => $free,
559
+            'used' => $used,
560
+            'quota' => $quota,
561
+            'total' => $total,
562
+            'relative' => $relative,
563
+            'owner' => $ownerId,
564
+            'ownerDisplayName' => $ownerDisplayName,
565
+            'mountType' => $mount->getMountType(),
566
+            'mountPoint' => trim($mountPoint, '/'),
567
+        ];
568
+    }
569
+
570
+    /**
571
+     * Get storage info including all mount points and quota
572
+     *
573
+     * @param int $quota
574
+     * @return array
575
+     */
576
+    private static function getGlobalStorageInfo($quota) {
577
+        $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
578
+        $used = $rootInfo['size'];
579
+        if ($used < 0) {
580
+            $used = 0;
581
+        }
582
+
583
+        $total = $quota;
584
+        $free = $quota - $used;
585
+
586
+        if ($total > 0) {
587
+            if ($quota > 0 && $total > $quota) {
588
+                $total = $quota;
589
+            }
590
+            // prevent division by zero or error codes (negative values)
591
+            $relative = round(($used / $total) * 10000) / 100;
592
+        } else {
593
+            $relative = 0;
594
+        }
595
+
596
+        return [
597
+            'free' => $free,
598
+            'used' => $used,
599
+            'total' => $total,
600
+            'relative' => $relative,
601
+            'quota' => $quota
602
+        ];
603
+    }
604
+
605
+    /**
606
+     * Returns whether the config file is set manually to read-only
607
+     * @return bool
608
+     */
609
+    public static function isReadOnlyConfigEnabled() {
610
+        return \OC::$server->getConfig()->getSystemValue('config_is_read_only', false);
611
+    }
612 612
 }
Please login to merge, or discard this patch.
apps/settings/templates/settings/admin/server.php 1 patch
Indentation   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -30,19 +30,19 @@  discard block
 block discarded – undo
30 30
 	<h2 class="inlineblock"><?php p($l->t('Background jobs'));?></h2>
31 31
 	<p class="cronlog inlineblock">
32 32
 		<?php if ($_['lastcron'] !== false) {
33
-	$relative_time = relative_modified_date($_['lastcron']);
34
-	$maxAgeRelativeTime = relative_modified_date($_['cronMaxAge']);
33
+    $relative_time = relative_modified_date($_['lastcron']);
34
+    $maxAgeRelativeTime = relative_modified_date($_['cronMaxAge']);
35 35
 
36
-	$formatter = \OC::$server->getDateTimeFormatter();
37
-	$absolute_time = $formatter->formatDateTime($_['lastcron'], 'long', 'long');
38
-	$maxAgeAbsoluteTime = $formatter->formatDateTime($_['cronMaxAge'], 'long', 'long');
39
-	if (time() - $_['lastcron'] > 600) { ?>
36
+    $formatter = \OC::$server->getDateTimeFormatter();
37
+    $absolute_time = $formatter->formatDateTime($_['lastcron'], 'long', 'long');
38
+    $maxAgeAbsoluteTime = $formatter->formatDateTime($_['cronMaxAge'], 'long', 'long');
39
+    if (time() - $_['lastcron'] > 600) { ?>
40 40
 				<span class="status error"></span>
41 41
 				<span class="crondate" title="<?php p($absolute_time);?>">
42 42
 					<?php p($l->t("Last job execution ran %s. Something seems wrong.", [$relative_time]));?>
43 43
 				</span>
44 44
 			<?php } elseif (time() - $_['cronMaxAge'] > 12 * 3600) {
45
-		if ($_['backgroundjobs_mode'] === 'cron') { ?>
45
+        if ($_['backgroundjobs_mode'] === 'cron') { ?>
46 46
 						<span class="status warning"></span>
47 47
 						<span class="crondate" title="<?php p($maxAgeAbsoluteTime);?>">
48 48
 							<?php p($l->t("Some jobs haven’t been executed since %s. Please consider increasing the execution frequency.", [$maxAgeRelativeTime]));?>
@@ -53,7 +53,7 @@  discard block
 block discarded – undo
53 53
 							<?php p($l->t("Some jobs didn’t execute since %s. Please consider switching to system cron.", [$maxAgeRelativeTime]));?>
54 54
 						</span>
55 55
 					<?php }
56
-	} else { ?>
56
+    } else { ?>
57 57
 				<span class="status success"></span>
58 58
 				<span class="crondate" title="<?php p($absolute_time);?>">
59 59
 					<?php p($l->t("Last job ran %s.", [$relative_time]));?>
@@ -62,7 +62,7 @@  discard block
 block discarded – undo
62 62
 } else { ?>
63 63
 			<span class="status error"></span>
64 64
 			<?php p($l->t("Background job didn’t run yet!"));
65
-		} ?>
65
+        } ?>
66 66
 	</p>
67 67
 	<a target="_blank" rel="noreferrer noopener" class="icon-info"
68 68
 	   title="<?php p($l->t('Open documentation'));?>"
@@ -75,38 +75,38 @@  discard block
 block discarded – undo
75 75
 			<p>
76 76
 				<input type="radio" name="mode" value="ajax" class="radio"
77 77
 					   id="backgroundjobs_ajax" <?php if ($_['backgroundjobs_mode'] === "ajax") {
78
-			print_unescaped('checked="checked"');
79
-		} ?>>
78
+            print_unescaped('checked="checked"');
79
+        } ?>>
80 80
 				<label for="backgroundjobs_ajax">AJAX</label><br/>
81 81
 				<em><?php p($l->t("Execute one task with each page loaded.")); ?></em>
82 82
 			</p>
83 83
 			<p>
84 84
 				<input type="radio" name="mode" value="webcron" class="radio"
85 85
 					   id="backgroundjobs_webcron" <?php if ($_['backgroundjobs_mode'] === "webcron") {
86
-			print_unescaped('checked="checked"');
87
-		} ?>>
86
+            print_unescaped('checked="checked"');
87
+        } ?>>
88 88
 				<label for="backgroundjobs_webcron">Webcron</label><br/>
89 89
 				<em><?php p($l->t("cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP.")); ?></em>
90 90
 			</p>
91 91
 			<p>
92 92
 				<input type="radio" name="mode" value="cron" class="radio"
93 93
 					   id="backgroundjobs_cron" <?php if ($_['backgroundjobs_mode'] === "cron") {
94
-			print_unescaped('checked="checked"');
95
-		}
96
-				if (!$_['cli_based_cron_possible']) {
97
-					print_unescaped('disabled');
98
-				}?>>
94
+            print_unescaped('checked="checked"');
95
+        }
96
+                if (!$_['cli_based_cron_possible']) {
97
+                    print_unescaped('disabled');
98
+                }?>>
99 99
 				<label for="backgroundjobs_cron">Cron</label><br/>
100 100
 				<em><?php p($l->t("Use system cron service to call the cron.php file every 5 minutes.")); ?>
101 101
 					<?php if ($_['cli_based_cron_possible']) {
102
-					p($l->t('The cron.php needs to be executed by the system user "%s".', [$_['cli_based_cron_user']]));
103
-				} else {
104
-					print_unescaped(str_replace(
105
-							['{linkstart}', '{linkend}'],
106
-							['<a href="https://www.php.net/manual/en/book.posix.php">', ' ↗</a>'],
107
-							$l->t('To run this you need the PHP POSIX extension. See {linkstart}PHP documentation{linkend} for more details.')
108
-						));
109
-				} ?></em>
102
+                    p($l->t('The cron.php needs to be executed by the system user "%s".', [$_['cli_based_cron_user']]));
103
+                } else {
104
+                    print_unescaped(str_replace(
105
+                            ['{linkstart}', '{linkend}'],
106
+                            ['<a href="https://www.php.net/manual/en/book.posix.php">', ' ↗</a>'],
107
+                            $l->t('To run this you need the PHP POSIX extension. See {linkstart}PHP documentation{linkend} for more details.')
108
+                        ));
109
+                } ?></em>
110 110
 
111 111
 			</p>
112 112
 		</fieldset>
Please login to merge, or discard this patch.