Passed
Push — master ( 7e784a...fda6ff )
by Roeland
11:29 queued 11s
created
lib/private/AppConfig.php 2 patches
Indentation   +314 added lines, -314 removed lines patch added patch discarded remove patch
@@ -43,328 +43,328 @@
 block discarded – undo
43 43
  */
44 44
 class AppConfig implements IAppConfig {
45 45
 
46
-	/** @var array[] */
47
-	protected $sensitiveValues = [
48
-		'external' => [
49
-			'/^sites$/',
50
-		],
51
-		'spreed' => [
52
-			'/^bridge_bot_password/',
53
-			'/^signaling_servers$/',
54
-			'/^signaling_ticket_secret$/',
55
-			'/^stun_servers$/',
56
-			'/^turn_servers$/',
57
-			'/^turn_server_secret$/',
58
-		],
59
-		'theming' => [
60
-			'/^imprintUrl$/',
61
-			'/^privacyUrl$/',
62
-			'/^slogan$/',
63
-			'/^url$/',
64
-		],
65
-		'user_ldap' => [
66
-			'/^(s..)?ldap_agent_password$/',
67
-		],
68
-	];
69
-
70
-	/** @var \OCP\IDBConnection */
71
-	protected $conn;
72
-
73
-	/** @var array[] */
74
-	private $cache = [];
75
-
76
-	/** @var bool */
77
-	private $configLoaded = false;
78
-
79
-	/**
80
-	 * @param IDBConnection $conn
81
-	 */
82
-	public function __construct(IDBConnection $conn) {
83
-		$this->conn = $conn;
84
-		$this->configLoaded = false;
85
-	}
86
-
87
-	/**
88
-	 * @param string $app
89
-	 * @return array
90
-	 */
91
-	private function getAppValues($app) {
92
-		$this->loadConfigValues();
93
-
94
-		if (isset($this->cache[$app])) {
95
-			return $this->cache[$app];
96
-		}
97
-
98
-		return [];
99
-	}
100
-
101
-	/**
102
-	 * Get all apps using the config
103
-	 *
104
-	 * @return array an array of app ids
105
-	 *
106
-	 * This function returns a list of all apps that have at least one
107
-	 * entry in the appconfig table.
108
-	 */
109
-	public function getApps() {
110
-		$this->loadConfigValues();
111
-
112
-		return $this->getSortedKeys($this->cache);
113
-	}
114
-
115
-	/**
116
-	 * Get the available keys for an app
117
-	 *
118
-	 * @param string $app the app we are looking for
119
-	 * @return array an array of key names
120
-	 *
121
-	 * This function gets all keys of an app. Please note that the values are
122
-	 * not returned.
123
-	 */
124
-	public function getKeys($app) {
125
-		$this->loadConfigValues();
126
-
127
-		if (isset($this->cache[$app])) {
128
-			return $this->getSortedKeys($this->cache[$app]);
129
-		}
130
-
131
-		return [];
132
-	}
133
-
134
-	public function getSortedKeys($data) {
135
-		$keys = array_keys($data);
136
-		sort($keys);
137
-		return $keys;
138
-	}
139
-
140
-	/**
141
-	 * Gets the config value
142
-	 *
143
-	 * @param string $app app
144
-	 * @param string $key key
145
-	 * @param string $default = null, default value if the key does not exist
146
-	 * @return string the value or $default
147
-	 *
148
-	 * This function gets a value from the appconfig table. If the key does
149
-	 * not exist the default value will be returned
150
-	 */
151
-	public function getValue($app, $key, $default = null) {
152
-		$this->loadConfigValues();
153
-
154
-		if ($this->hasKey($app, $key)) {
155
-			return $this->cache[$app][$key];
156
-		}
157
-
158
-		return $default;
159
-	}
160
-
161
-	/**
162
-	 * check if a key is set in the appconfig
163
-	 *
164
-	 * @param string $app
165
-	 * @param string $key
166
-	 * @return bool
167
-	 */
168
-	public function hasKey($app, $key) {
169
-		$this->loadConfigValues();
170
-
171
-		return isset($this->cache[$app][$key]);
172
-	}
173
-
174
-	/**
175
-	 * Sets a value. If the key did not exist before it will be created.
176
-	 *
177
-	 * @param string $app app
178
-	 * @param string $key key
179
-	 * @param string|float|int $value value
180
-	 * @return bool True if the value was inserted or updated, false if the value was the same
181
-	 */
182
-	public function setValue($app, $key, $value) {
183
-		if (!$this->hasKey($app, $key)) {
184
-			$inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*appconfig', [
185
-				'appid' => $app,
186
-				'configkey' => $key,
187
-				'configvalue' => $value,
188
-			], [
189
-				'appid',
190
-				'configkey',
191
-			]);
192
-
193
-			if ($inserted) {
194
-				if (!isset($this->cache[$app])) {
195
-					$this->cache[$app] = [];
196
-				}
197
-
198
-				$this->cache[$app][$key] = $value;
199
-				return true;
200
-			}
201
-		}
202
-
203
-		$sql = $this->conn->getQueryBuilder();
204
-		$sql->update('appconfig')
205
-			->set('configvalue', $sql->createNamedParameter($value))
206
-			->where($sql->expr()->eq('appid', $sql->createNamedParameter($app)))
207
-			->andWhere($sql->expr()->eq('configkey', $sql->createNamedParameter($key)));
208
-
209
-		/*
46
+    /** @var array[] */
47
+    protected $sensitiveValues = [
48
+        'external' => [
49
+            '/^sites$/',
50
+        ],
51
+        'spreed' => [
52
+            '/^bridge_bot_password/',
53
+            '/^signaling_servers$/',
54
+            '/^signaling_ticket_secret$/',
55
+            '/^stun_servers$/',
56
+            '/^turn_servers$/',
57
+            '/^turn_server_secret$/',
58
+        ],
59
+        'theming' => [
60
+            '/^imprintUrl$/',
61
+            '/^privacyUrl$/',
62
+            '/^slogan$/',
63
+            '/^url$/',
64
+        ],
65
+        'user_ldap' => [
66
+            '/^(s..)?ldap_agent_password$/',
67
+        ],
68
+    ];
69
+
70
+    /** @var \OCP\IDBConnection */
71
+    protected $conn;
72
+
73
+    /** @var array[] */
74
+    private $cache = [];
75
+
76
+    /** @var bool */
77
+    private $configLoaded = false;
78
+
79
+    /**
80
+     * @param IDBConnection $conn
81
+     */
82
+    public function __construct(IDBConnection $conn) {
83
+        $this->conn = $conn;
84
+        $this->configLoaded = false;
85
+    }
86
+
87
+    /**
88
+     * @param string $app
89
+     * @return array
90
+     */
91
+    private function getAppValues($app) {
92
+        $this->loadConfigValues();
93
+
94
+        if (isset($this->cache[$app])) {
95
+            return $this->cache[$app];
96
+        }
97
+
98
+        return [];
99
+    }
100
+
101
+    /**
102
+     * Get all apps using the config
103
+     *
104
+     * @return array an array of app ids
105
+     *
106
+     * This function returns a list of all apps that have at least one
107
+     * entry in the appconfig table.
108
+     */
109
+    public function getApps() {
110
+        $this->loadConfigValues();
111
+
112
+        return $this->getSortedKeys($this->cache);
113
+    }
114
+
115
+    /**
116
+     * Get the available keys for an app
117
+     *
118
+     * @param string $app the app we are looking for
119
+     * @return array an array of key names
120
+     *
121
+     * This function gets all keys of an app. Please note that the values are
122
+     * not returned.
123
+     */
124
+    public function getKeys($app) {
125
+        $this->loadConfigValues();
126
+
127
+        if (isset($this->cache[$app])) {
128
+            return $this->getSortedKeys($this->cache[$app]);
129
+        }
130
+
131
+        return [];
132
+    }
133
+
134
+    public function getSortedKeys($data) {
135
+        $keys = array_keys($data);
136
+        sort($keys);
137
+        return $keys;
138
+    }
139
+
140
+    /**
141
+     * Gets the config value
142
+     *
143
+     * @param string $app app
144
+     * @param string $key key
145
+     * @param string $default = null, default value if the key does not exist
146
+     * @return string the value or $default
147
+     *
148
+     * This function gets a value from the appconfig table. If the key does
149
+     * not exist the default value will be returned
150
+     */
151
+    public function getValue($app, $key, $default = null) {
152
+        $this->loadConfigValues();
153
+
154
+        if ($this->hasKey($app, $key)) {
155
+            return $this->cache[$app][$key];
156
+        }
157
+
158
+        return $default;
159
+    }
160
+
161
+    /**
162
+     * check if a key is set in the appconfig
163
+     *
164
+     * @param string $app
165
+     * @param string $key
166
+     * @return bool
167
+     */
168
+    public function hasKey($app, $key) {
169
+        $this->loadConfigValues();
170
+
171
+        return isset($this->cache[$app][$key]);
172
+    }
173
+
174
+    /**
175
+     * Sets a value. If the key did not exist before it will be created.
176
+     *
177
+     * @param string $app app
178
+     * @param string $key key
179
+     * @param string|float|int $value value
180
+     * @return bool True if the value was inserted or updated, false if the value was the same
181
+     */
182
+    public function setValue($app, $key, $value) {
183
+        if (!$this->hasKey($app, $key)) {
184
+            $inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*appconfig', [
185
+                'appid' => $app,
186
+                'configkey' => $key,
187
+                'configvalue' => $value,
188
+            ], [
189
+                'appid',
190
+                'configkey',
191
+            ]);
192
+
193
+            if ($inserted) {
194
+                if (!isset($this->cache[$app])) {
195
+                    $this->cache[$app] = [];
196
+                }
197
+
198
+                $this->cache[$app][$key] = $value;
199
+                return true;
200
+            }
201
+        }
202
+
203
+        $sql = $this->conn->getQueryBuilder();
204
+        $sql->update('appconfig')
205
+            ->set('configvalue', $sql->createNamedParameter($value))
206
+            ->where($sql->expr()->eq('appid', $sql->createNamedParameter($app)))
207
+            ->andWhere($sql->expr()->eq('configkey', $sql->createNamedParameter($key)));
208
+
209
+        /*
210 210
 		 * Only limit to the existing value for non-Oracle DBs:
211 211
 		 * http://docs.oracle.com/cd/E11882_01/server.112/e26088/conditions002.htm#i1033286
212 212
 		 * > Large objects (LOBs) are not supported in comparison conditions.
213 213
 		 */
214
-		if (!($this->conn instanceof OracleConnection)) {
214
+        if (!($this->conn instanceof OracleConnection)) {
215 215
 
216
-			/*
216
+            /*
217 217
 			 * Only update the value when it is not the same
218 218
 			 * Note that NULL requires some special handling. Since comparing
219 219
 			 * against null can have special results.
220 220
 			 */
221 221
 
222
-			if ($value === null) {
223
-				$sql->andWhere(
224
-					$sql->expr()->isNotNull('configvalue')
225
-				);
226
-			} else {
227
-				$sql->andWhere(
228
-					$sql->expr()->orX(
229
-						$sql->expr()->isNull('configvalue'),
230
-						$sql->expr()->neq('configvalue', $sql->createNamedParameter($value))
231
-					)
232
-				);
233
-			}
234
-		}
235
-
236
-		$changedRow = (bool) $sql->execute();
237
-
238
-		$this->cache[$app][$key] = $value;
239
-
240
-		return $changedRow;
241
-	}
242
-
243
-	/**
244
-	 * Deletes a key
245
-	 *
246
-	 * @param string $app app
247
-	 * @param string $key key
248
-	 * @return boolean
249
-	 */
250
-	public function deleteKey($app, $key) {
251
-		$this->loadConfigValues();
252
-
253
-		$sql = $this->conn->getQueryBuilder();
254
-		$sql->delete('appconfig')
255
-			->where($sql->expr()->eq('appid', $sql->createParameter('app')))
256
-			->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
257
-			->setParameter('app', $app)
258
-			->setParameter('configkey', $key);
259
-		$sql->execute();
260
-
261
-		unset($this->cache[$app][$key]);
262
-		return false;
263
-	}
264
-
265
-	/**
266
-	 * Remove app from appconfig
267
-	 *
268
-	 * @param string $app app
269
-	 * @return boolean
270
-	 *
271
-	 * Removes all keys in appconfig belonging to the app.
272
-	 */
273
-	public function deleteApp($app) {
274
-		$this->loadConfigValues();
275
-
276
-		$sql = $this->conn->getQueryBuilder();
277
-		$sql->delete('appconfig')
278
-			->where($sql->expr()->eq('appid', $sql->createParameter('app')))
279
-			->setParameter('app', $app);
280
-		$sql->execute();
281
-
282
-		unset($this->cache[$app]);
283
-		return false;
284
-	}
285
-
286
-	/**
287
-	 * get multiple values, either the app or key can be used as wildcard by setting it to false
288
-	 *
289
-	 * @param string|false $app
290
-	 * @param string|false $key
291
-	 * @return array|false
292
-	 */
293
-	public function getValues($app, $key) {
294
-		if (($app !== false) === ($key !== false)) {
295
-			return false;
296
-		}
297
-
298
-		if ($key === false) {
299
-			return $this->getAppValues($app);
300
-		} else {
301
-			$appIds = $this->getApps();
302
-			$values = array_map(function ($appId) use ($key) {
303
-				return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null;
304
-			}, $appIds);
305
-			$result = array_combine($appIds, $values);
306
-
307
-			return array_filter($result);
308
-		}
309
-	}
310
-
311
-	/**
312
-	 * get all values of the app or and filters out sensitive data
313
-	 *
314
-	 * @param string $app
315
-	 * @return array
316
-	 */
317
-	public function getFilteredValues($app) {
318
-		$values = $this->getValues($app, false);
319
-
320
-		if (isset($this->sensitiveValues[$app])) {
321
-			foreach ($this->sensitiveValues[$app] as $sensitiveKeyExp) {
322
-				$sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
323
-				foreach ($sensitiveKeys as $sensitiveKey) {
324
-					$values[$sensitiveKey] = IConfig::SENSITIVE_VALUE;
325
-				}
326
-			}
327
-		}
328
-
329
-		return $values;
330
-	}
331
-
332
-	/**
333
-	 * Load all the app config values
334
-	 */
335
-	protected function loadConfigValues() {
336
-		if ($this->configLoaded) {
337
-			return;
338
-		}
339
-
340
-		$this->cache = [];
341
-
342
-		$sql = $this->conn->getQueryBuilder();
343
-		$sql->select('*')
344
-			->from('appconfig');
345
-		$result = $sql->execute();
346
-
347
-		// we are going to store the result in memory anyway
348
-		$rows = $result->fetchAll();
349
-		foreach ($rows as $row) {
350
-			if (!isset($this->cache[$row['appid']])) {
351
-				$this->cache[(string)$row['appid']] = [];
352
-			}
353
-
354
-			$this->cache[(string)$row['appid']][(string)$row['configkey']] = (string)$row['configvalue'];
355
-		}
356
-		$result->closeCursor();
357
-
358
-		$this->configLoaded = true;
359
-	}
360
-
361
-	/**
362
-	 * Clear all the cached app config values
363
-	 *
364
-	 * WARNING: do not use this - this is only for usage with the SCSSCacher to
365
-	 * clear the memory cache of the app config
366
-	 */
367
-	public function clearCachedConfig() {
368
-		$this->configLoaded = false;
369
-	}
222
+            if ($value === null) {
223
+                $sql->andWhere(
224
+                    $sql->expr()->isNotNull('configvalue')
225
+                );
226
+            } else {
227
+                $sql->andWhere(
228
+                    $sql->expr()->orX(
229
+                        $sql->expr()->isNull('configvalue'),
230
+                        $sql->expr()->neq('configvalue', $sql->createNamedParameter($value))
231
+                    )
232
+                );
233
+            }
234
+        }
235
+
236
+        $changedRow = (bool) $sql->execute();
237
+
238
+        $this->cache[$app][$key] = $value;
239
+
240
+        return $changedRow;
241
+    }
242
+
243
+    /**
244
+     * Deletes a key
245
+     *
246
+     * @param string $app app
247
+     * @param string $key key
248
+     * @return boolean
249
+     */
250
+    public function deleteKey($app, $key) {
251
+        $this->loadConfigValues();
252
+
253
+        $sql = $this->conn->getQueryBuilder();
254
+        $sql->delete('appconfig')
255
+            ->where($sql->expr()->eq('appid', $sql->createParameter('app')))
256
+            ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
257
+            ->setParameter('app', $app)
258
+            ->setParameter('configkey', $key);
259
+        $sql->execute();
260
+
261
+        unset($this->cache[$app][$key]);
262
+        return false;
263
+    }
264
+
265
+    /**
266
+     * Remove app from appconfig
267
+     *
268
+     * @param string $app app
269
+     * @return boolean
270
+     *
271
+     * Removes all keys in appconfig belonging to the app.
272
+     */
273
+    public function deleteApp($app) {
274
+        $this->loadConfigValues();
275
+
276
+        $sql = $this->conn->getQueryBuilder();
277
+        $sql->delete('appconfig')
278
+            ->where($sql->expr()->eq('appid', $sql->createParameter('app')))
279
+            ->setParameter('app', $app);
280
+        $sql->execute();
281
+
282
+        unset($this->cache[$app]);
283
+        return false;
284
+    }
285
+
286
+    /**
287
+     * get multiple values, either the app or key can be used as wildcard by setting it to false
288
+     *
289
+     * @param string|false $app
290
+     * @param string|false $key
291
+     * @return array|false
292
+     */
293
+    public function getValues($app, $key) {
294
+        if (($app !== false) === ($key !== false)) {
295
+            return false;
296
+        }
297
+
298
+        if ($key === false) {
299
+            return $this->getAppValues($app);
300
+        } else {
301
+            $appIds = $this->getApps();
302
+            $values = array_map(function ($appId) use ($key) {
303
+                return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null;
304
+            }, $appIds);
305
+            $result = array_combine($appIds, $values);
306
+
307
+            return array_filter($result);
308
+        }
309
+    }
310
+
311
+    /**
312
+     * get all values of the app or and filters out sensitive data
313
+     *
314
+     * @param string $app
315
+     * @return array
316
+     */
317
+    public function getFilteredValues($app) {
318
+        $values = $this->getValues($app, false);
319
+
320
+        if (isset($this->sensitiveValues[$app])) {
321
+            foreach ($this->sensitiveValues[$app] as $sensitiveKeyExp) {
322
+                $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
323
+                foreach ($sensitiveKeys as $sensitiveKey) {
324
+                    $values[$sensitiveKey] = IConfig::SENSITIVE_VALUE;
325
+                }
326
+            }
327
+        }
328
+
329
+        return $values;
330
+    }
331
+
332
+    /**
333
+     * Load all the app config values
334
+     */
335
+    protected function loadConfigValues() {
336
+        if ($this->configLoaded) {
337
+            return;
338
+        }
339
+
340
+        $this->cache = [];
341
+
342
+        $sql = $this->conn->getQueryBuilder();
343
+        $sql->select('*')
344
+            ->from('appconfig');
345
+        $result = $sql->execute();
346
+
347
+        // we are going to store the result in memory anyway
348
+        $rows = $result->fetchAll();
349
+        foreach ($rows as $row) {
350
+            if (!isset($this->cache[$row['appid']])) {
351
+                $this->cache[(string)$row['appid']] = [];
352
+            }
353
+
354
+            $this->cache[(string)$row['appid']][(string)$row['configkey']] = (string)$row['configvalue'];
355
+        }
356
+        $result->closeCursor();
357
+
358
+        $this->configLoaded = true;
359
+    }
360
+
361
+    /**
362
+     * Clear all the cached app config values
363
+     *
364
+     * WARNING: do not use this - this is only for usage with the SCSSCacher to
365
+     * clear the memory cache of the app config
366
+     */
367
+    public function clearCachedConfig() {
368
+        $this->configLoaded = false;
369
+    }
370 370
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -299,7 +299,7 @@  discard block
 block discarded – undo
299 299
 			return $this->getAppValues($app);
300 300
 		} else {
301 301
 			$appIds = $this->getApps();
302
-			$values = array_map(function ($appId) use ($key) {
302
+			$values = array_map(function($appId) use ($key) {
303 303
 				return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null;
304 304
 			}, $appIds);
305 305
 			$result = array_combine($appIds, $values);
@@ -348,10 +348,10 @@  discard block
 block discarded – undo
348 348
 		$rows = $result->fetchAll();
349 349
 		foreach ($rows as $row) {
350 350
 			if (!isset($this->cache[$row['appid']])) {
351
-				$this->cache[(string)$row['appid']] = [];
351
+				$this->cache[(string) $row['appid']] = [];
352 352
 			}
353 353
 
354
-			$this->cache[(string)$row['appid']][(string)$row['configkey']] = (string)$row['configvalue'];
354
+			$this->cache[(string) $row['appid']][(string) $row['configkey']] = (string) $row['configvalue'];
355 355
 		}
356 356
 		$result->closeCursor();
357 357
 
Please login to merge, or discard this patch.
lib/private/Lock/MemcacheLockingProvider.php 1 patch
Indentation   +112 added lines, -112 removed lines patch added patch discarded remove patch
@@ -33,124 +33,124 @@
 block discarded – undo
33 33
 use OCP\Lock\LockedException;
34 34
 
35 35
 class MemcacheLockingProvider extends AbstractLockingProvider {
36
-	/**
37
-	 * @var \OCP\IMemcache
38
-	 */
39
-	private $memcache;
36
+    /**
37
+     * @var \OCP\IMemcache
38
+     */
39
+    private $memcache;
40 40
 
41
-	/**
42
-	 * @param \OCP\IMemcache $memcache
43
-	 * @param int $ttl
44
-	 */
45
-	public function __construct(IMemcache $memcache, int $ttl = 3600) {
46
-		$this->memcache = $memcache;
47
-		$this->ttl = $ttl;
48
-	}
41
+    /**
42
+     * @param \OCP\IMemcache $memcache
43
+     * @param int $ttl
44
+     */
45
+    public function __construct(IMemcache $memcache, int $ttl = 3600) {
46
+        $this->memcache = $memcache;
47
+        $this->ttl = $ttl;
48
+    }
49 49
 
50
-	private function setTTL(string $path) {
51
-		if ($this->memcache instanceof IMemcacheTTL) {
52
-			$this->memcache->setTTL($path, $this->ttl);
53
-		}
54
-	}
50
+    private function setTTL(string $path) {
51
+        if ($this->memcache instanceof IMemcacheTTL) {
52
+            $this->memcache->setTTL($path, $this->ttl);
53
+        }
54
+    }
55 55
 
56
-	/**
57
-	 * @param string $path
58
-	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
59
-	 * @return bool
60
-	 */
61
-	public function isLocked(string $path, int $type): bool {
62
-		$lockValue = $this->memcache->get($path);
63
-		if ($type === self::LOCK_SHARED) {
64
-			return is_int($lockValue) && $lockValue > 0;
65
-		} elseif ($type === self::LOCK_EXCLUSIVE) {
66
-			return $lockValue === 'exclusive';
67
-		} else {
68
-			return false;
69
-		}
70
-	}
56
+    /**
57
+     * @param string $path
58
+     * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
59
+     * @return bool
60
+     */
61
+    public function isLocked(string $path, int $type): bool {
62
+        $lockValue = $this->memcache->get($path);
63
+        if ($type === self::LOCK_SHARED) {
64
+            return is_int($lockValue) && $lockValue > 0;
65
+        } elseif ($type === self::LOCK_EXCLUSIVE) {
66
+            return $lockValue === 'exclusive';
67
+        } else {
68
+            return false;
69
+        }
70
+    }
71 71
 
72
-	/**
73
-	 * @param string $path
74
-	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
75
-	 * @param string $readablePath human readable path to use in error messages
76
-	 * @throws \OCP\Lock\LockedException
77
-	 */
78
-	public function acquireLock(string $path, int $type, string $readablePath = null) {
79
-		if ($type === self::LOCK_SHARED) {
80
-			if (!$this->memcache->inc($path)) {
81
-				throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath);
82
-			}
83
-		} else {
84
-			$this->memcache->add($path, 0);
85
-			if (!$this->memcache->cas($path, 0, 'exclusive')) {
86
-				throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath);
87
-			}
88
-		}
89
-		$this->setTTL($path);
90
-		$this->markAcquire($path, $type);
91
-	}
72
+    /**
73
+     * @param string $path
74
+     * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
75
+     * @param string $readablePath human readable path to use in error messages
76
+     * @throws \OCP\Lock\LockedException
77
+     */
78
+    public function acquireLock(string $path, int $type, string $readablePath = null) {
79
+        if ($type === self::LOCK_SHARED) {
80
+            if (!$this->memcache->inc($path)) {
81
+                throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath);
82
+            }
83
+        } else {
84
+            $this->memcache->add($path, 0);
85
+            if (!$this->memcache->cas($path, 0, 'exclusive')) {
86
+                throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath);
87
+            }
88
+        }
89
+        $this->setTTL($path);
90
+        $this->markAcquire($path, $type);
91
+    }
92 92
 
93
-	/**
94
-	 * @param string $path
95
-	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
96
-	 */
97
-	public function releaseLock(string $path, int $type) {
98
-		if ($type === self::LOCK_SHARED) {
99
-			$ownSharedLockCount = $this->getOwnSharedLockCount($path);
100
-			$newValue = 0;
101
-			if ($ownSharedLockCount === 0) { // if we are not holding the lock, don't try to release it
102
-				return;
103
-			}
104
-			if ($ownSharedLockCount === 1) {
105
-				$removed = $this->memcache->cad($path, 1); // if we're the only one having a shared lock we can remove it in one go
106
-				if (!$removed) { //someone else also has a shared lock, decrease only
107
-					$newValue = $this->memcache->dec($path);
108
-				}
109
-			} else {
110
-				// if we own more than one lock ourselves just decrease
111
-				$newValue = $this->memcache->dec($path);
112
-			}
93
+    /**
94
+     * @param string $path
95
+     * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
96
+     */
97
+    public function releaseLock(string $path, int $type) {
98
+        if ($type === self::LOCK_SHARED) {
99
+            $ownSharedLockCount = $this->getOwnSharedLockCount($path);
100
+            $newValue = 0;
101
+            if ($ownSharedLockCount === 0) { // if we are not holding the lock, don't try to release it
102
+                return;
103
+            }
104
+            if ($ownSharedLockCount === 1) {
105
+                $removed = $this->memcache->cad($path, 1); // if we're the only one having a shared lock we can remove it in one go
106
+                if (!$removed) { //someone else also has a shared lock, decrease only
107
+                    $newValue = $this->memcache->dec($path);
108
+                }
109
+            } else {
110
+                // if we own more than one lock ourselves just decrease
111
+                $newValue = $this->memcache->dec($path);
112
+            }
113 113
 
114
-			// if we somehow release more locks then exists, reset the lock
115
-			if ($newValue < 0) {
116
-				$this->memcache->cad($path, $newValue);
117
-			}
118
-		} elseif ($type === self::LOCK_EXCLUSIVE) {
119
-			$this->memcache->cad($path, 'exclusive');
120
-		}
121
-		$this->markRelease($path, $type);
122
-	}
114
+            // if we somehow release more locks then exists, reset the lock
115
+            if ($newValue < 0) {
116
+                $this->memcache->cad($path, $newValue);
117
+            }
118
+        } elseif ($type === self::LOCK_EXCLUSIVE) {
119
+            $this->memcache->cad($path, 'exclusive');
120
+        }
121
+        $this->markRelease($path, $type);
122
+    }
123 123
 
124
-	/**
125
-	 * Change the type of an existing lock
126
-	 *
127
-	 * @param string $path
128
-	 * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
129
-	 * @throws \OCP\Lock\LockedException
130
-	 */
131
-	public function changeLock(string $path, int $targetType) {
132
-		if ($targetType === self::LOCK_SHARED) {
133
-			if (!$this->memcache->cas($path, 'exclusive', 1)) {
134
-				throw new LockedException($path, null, $this->getExistingLockForException($path));
135
-			}
136
-		} elseif ($targetType === self::LOCK_EXCLUSIVE) {
137
-			// we can only change a shared lock to an exclusive if there's only a single owner of the shared lock
138
-			if (!$this->memcache->cas($path, 1, 'exclusive')) {
139
-				throw new LockedException($path, null, $this->getExistingLockForException($path));
140
-			}
141
-		}
142
-		$this->setTTL($path);
143
-		$this->markChange($path, $targetType);
144
-	}
124
+    /**
125
+     * Change the type of an existing lock
126
+     *
127
+     * @param string $path
128
+     * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
129
+     * @throws \OCP\Lock\LockedException
130
+     */
131
+    public function changeLock(string $path, int $targetType) {
132
+        if ($targetType === self::LOCK_SHARED) {
133
+            if (!$this->memcache->cas($path, 'exclusive', 1)) {
134
+                throw new LockedException($path, null, $this->getExistingLockForException($path));
135
+            }
136
+        } elseif ($targetType === self::LOCK_EXCLUSIVE) {
137
+            // we can only change a shared lock to an exclusive if there's only a single owner of the shared lock
138
+            if (!$this->memcache->cas($path, 1, 'exclusive')) {
139
+                throw new LockedException($path, null, $this->getExistingLockForException($path));
140
+            }
141
+        }
142
+        $this->setTTL($path);
143
+        $this->markChange($path, $targetType);
144
+    }
145 145
 
146
-	private function getExistingLockForException($path) {
147
-		$existing = $this->memcache->get($path);
148
-		if (!$existing) {
149
-			return 'none';
150
-		} elseif ($existing === 'exclusive') {
151
-			return $existing;
152
-		} else {
153
-			return $existing . ' shared locks';
154
-		}
155
-	}
146
+    private function getExistingLockForException($path) {
147
+        $existing = $this->memcache->get($path);
148
+        if (!$existing) {
149
+            return 'none';
150
+        } elseif ($existing === 'exclusive') {
151
+            return $existing;
152
+        } else {
153
+            return $existing . ' shared locks';
154
+        }
155
+    }
156 156
 }
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 http://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 http://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.
apps/encryption/lib/Crypto/Crypt.php 2 patches
Indentation   +662 added lines, -662 removed lines patch added patch discarded remove patch
@@ -57,666 +57,666 @@
 block discarded – undo
57 57
  * @package OCA\Encryption\Crypto
58 58
  */
59 59
 class Crypt {
60
-	public const DEFAULT_CIPHER = 'AES-256-CTR';
61
-	// default cipher from old Nextcloud versions
62
-	public const LEGACY_CIPHER = 'AES-128-CFB';
63
-
64
-	// default key format, old Nextcloud version encrypted the private key directly
65
-	// with the user password
66
-	public const LEGACY_KEY_FORMAT = 'password';
67
-
68
-	public const HEADER_START = 'HBEGIN';
69
-	public const HEADER_END = 'HEND';
70
-
71
-	/** @var ILogger */
72
-	private $logger;
73
-
74
-	/** @var string */
75
-	private $user;
76
-
77
-	/** @var IConfig */
78
-	private $config;
79
-
80
-	/** @var array */
81
-	private $supportedKeyFormats;
82
-
83
-	/** @var IL10N */
84
-	private $l;
85
-
86
-	/** @var array */
87
-	private $supportedCiphersAndKeySize = [
88
-		'AES-256-CTR' => 32,
89
-		'AES-128-CTR' => 16,
90
-		'AES-256-CFB' => 32,
91
-		'AES-128-CFB' => 16,
92
-	];
93
-
94
-	/** @var bool */
95
-	private $supportLegacy;
96
-
97
-	/**
98
-	 * @param ILogger $logger
99
-	 * @param IUserSession $userSession
100
-	 * @param IConfig $config
101
-	 * @param IL10N $l
102
-	 */
103
-	public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l) {
104
-		$this->logger = $logger;
105
-		$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
106
-		$this->config = $config;
107
-		$this->l = $l;
108
-		$this->supportedKeyFormats = ['hash', 'password'];
109
-
110
-		$this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
111
-	}
112
-
113
-	/**
114
-	 * create new private/public key-pair for user
115
-	 *
116
-	 * @return array|bool
117
-	 */
118
-	public function createKeyPair() {
119
-		$log = $this->logger;
120
-		$res = $this->getOpenSSLPKey();
121
-
122
-		if (!$res) {
123
-			$log->error("Encryption Library couldn't generate users key-pair for {$this->user}",
124
-				['app' => 'encryption']);
125
-
126
-			if (openssl_error_string()) {
127
-				$log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
128
-					['app' => 'encryption']);
129
-			}
130
-		} elseif (openssl_pkey_export($res,
131
-			$privateKey,
132
-			null,
133
-			$this->getOpenSSLConfig())) {
134
-			$keyDetails = openssl_pkey_get_details($res);
135
-			$publicKey = $keyDetails['key'];
136
-
137
-			return [
138
-				'publicKey' => $publicKey,
139
-				'privateKey' => $privateKey
140
-			];
141
-		}
142
-		$log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user,
143
-			['app' => 'encryption']);
144
-		if (openssl_error_string()) {
145
-			$log->error('Encryption Library:' . openssl_error_string(),
146
-				['app' => 'encryption']);
147
-		}
148
-
149
-		return false;
150
-	}
151
-
152
-	/**
153
-	 * Generates a new private key
154
-	 *
155
-	 * @return resource
156
-	 */
157
-	public function getOpenSSLPKey() {
158
-		$config = $this->getOpenSSLConfig();
159
-		return openssl_pkey_new($config);
160
-	}
161
-
162
-	/**
163
-	 * get openSSL Config
164
-	 *
165
-	 * @return array
166
-	 */
167
-	private function getOpenSSLConfig() {
168
-		$config = ['private_key_bits' => 4096];
169
-		$config = array_merge(
170
-			$config,
171
-			$this->config->getSystemValue('openssl', [])
172
-		);
173
-		return $config;
174
-	}
175
-
176
-	/**
177
-	 * @param string $plainContent
178
-	 * @param string $passPhrase
179
-	 * @param int $version
180
-	 * @param int $position
181
-	 * @return false|string
182
-	 * @throws EncryptionFailedException
183
-	 */
184
-	public function symmetricEncryptFileContent($plainContent, $passPhrase, $version, $position) {
185
-		if (!$plainContent) {
186
-			$this->logger->error('Encryption Library, symmetrical encryption failed no content given',
187
-				['app' => 'encryption']);
188
-			return false;
189
-		}
190
-
191
-		$iv = $this->generateIv();
192
-
193
-		$encryptedContent = $this->encrypt($plainContent,
194
-			$iv,
195
-			$passPhrase,
196
-			$this->getCipher());
197
-
198
-		// Create a signature based on the key as well as the current version
199
-		$sig = $this->createSignature($encryptedContent, $passPhrase.'_'.$version.'_'.$position);
200
-
201
-		// combine content to encrypt the IV identifier and actual IV
202
-		$catFile = $this->concatIV($encryptedContent, $iv);
203
-		$catFile = $this->concatSig($catFile, $sig);
204
-		return $this->addPadding($catFile);
205
-	}
206
-
207
-	/**
208
-	 * generate header for encrypted file
209
-	 *
210
-	 * @param string $keyFormat (can be 'hash' or 'password')
211
-	 * @return string
212
-	 * @throws \InvalidArgumentException
213
-	 */
214
-	public function generateHeader($keyFormat = 'hash') {
215
-		if (in_array($keyFormat, $this->supportedKeyFormats, true) === false) {
216
-			throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported');
217
-		}
218
-
219
-		$cipher = $this->getCipher();
220
-
221
-		$header = self::HEADER_START
222
-			. ':cipher:' . $cipher
223
-			. ':keyFormat:' . $keyFormat
224
-			. ':' . self::HEADER_END;
225
-
226
-		return $header;
227
-	}
228
-
229
-	/**
230
-	 * @param string $plainContent
231
-	 * @param string $iv
232
-	 * @param string $passPhrase
233
-	 * @param string $cipher
234
-	 * @return string
235
-	 * @throws EncryptionFailedException
236
-	 */
237
-	private function encrypt($plainContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) {
238
-		$encryptedContent = openssl_encrypt($plainContent,
239
-			$cipher,
240
-			$passPhrase,
241
-			false,
242
-			$iv);
243
-
244
-		if (!$encryptedContent) {
245
-			$error = 'Encryption (symmetric) of content failed';
246
-			$this->logger->error($error . openssl_error_string(),
247
-				['app' => 'encryption']);
248
-			throw new EncryptionFailedException($error);
249
-		}
250
-
251
-		return $encryptedContent;
252
-	}
253
-
254
-	/**
255
-	 * return Cipher either from config.php or the default cipher defined in
256
-	 * this class
257
-	 *
258
-	 * @return string
259
-	 */
260
-	public function getCipher() {
261
-		$cipher = $this->config->getSystemValue('cipher', self::DEFAULT_CIPHER);
262
-		if (!isset($this->supportedCiphersAndKeySize[$cipher])) {
263
-			$this->logger->warning(
264
-					sprintf(
265
-							'Unsupported cipher (%s) defined in config.php supported. Falling back to %s',
266
-							$cipher,
267
-							self::DEFAULT_CIPHER
268
-					),
269
-				['app' => 'encryption']);
270
-			$cipher = self::DEFAULT_CIPHER;
271
-		}
272
-
273
-		// Workaround for OpenSSL 0.9.8. Fallback to an old cipher that should work.
274
-		if (OPENSSL_VERSION_NUMBER < 0x1000101f) {
275
-			if ($cipher === 'AES-256-CTR' || $cipher === 'AES-128-CTR') {
276
-				$cipher = self::LEGACY_CIPHER;
277
-			}
278
-		}
279
-
280
-		return $cipher;
281
-	}
282
-
283
-	/**
284
-	 * get key size depending on the cipher
285
-	 *
286
-	 * @param string $cipher
287
-	 * @return int
288
-	 * @throws \InvalidArgumentException
289
-	 */
290
-	protected function getKeySize($cipher) {
291
-		if (isset($this->supportedCiphersAndKeySize[$cipher])) {
292
-			return $this->supportedCiphersAndKeySize[$cipher];
293
-		}
294
-
295
-		throw new \InvalidArgumentException(
296
-			sprintf(
297
-					'Unsupported cipher (%s) defined.',
298
-					$cipher
299
-			)
300
-		);
301
-	}
302
-
303
-	/**
304
-	 * get legacy cipher
305
-	 *
306
-	 * @return string
307
-	 */
308
-	public function getLegacyCipher() {
309
-		if (!$this->supportLegacy) {
310
-			throw new ServerNotAvailableException('Legacy cipher is no longer supported!');
311
-		}
312
-
313
-		return self::LEGACY_CIPHER;
314
-	}
315
-
316
-	/**
317
-	 * @param string $encryptedContent
318
-	 * @param string $iv
319
-	 * @return string
320
-	 */
321
-	private function concatIV($encryptedContent, $iv) {
322
-		return $encryptedContent . '00iv00' . $iv;
323
-	}
324
-
325
-	/**
326
-	 * @param string $encryptedContent
327
-	 * @param string $signature
328
-	 * @return string
329
-	 */
330
-	private function concatSig($encryptedContent, $signature) {
331
-		return $encryptedContent . '00sig00' . $signature;
332
-	}
333
-
334
-	/**
335
-	 * Note: This is _NOT_ a padding used for encryption purposes. It is solely
336
-	 * used to achieve the PHP stream size. It has _NOTHING_ to do with the
337
-	 * encrypted content and is not used in any crypto primitive.
338
-	 *
339
-	 * @param string $data
340
-	 * @return string
341
-	 */
342
-	private function addPadding($data) {
343
-		return $data . 'xxx';
344
-	}
345
-
346
-	/**
347
-	 * generate password hash used to encrypt the users private key
348
-	 *
349
-	 * @param string $password
350
-	 * @param string $cipher
351
-	 * @param string $uid only used for user keys
352
-	 * @return string
353
-	 */
354
-	protected function generatePasswordHash($password, $cipher, $uid = '') {
355
-		$instanceId = $this->config->getSystemValue('instanceid');
356
-		$instanceSecret = $this->config->getSystemValue('secret');
357
-		$salt = hash('sha256', $uid . $instanceId . $instanceSecret, true);
358
-		$keySize = $this->getKeySize($cipher);
359
-
360
-		$hash = hash_pbkdf2(
361
-			'sha256',
362
-			$password,
363
-			$salt,
364
-			100000,
365
-			$keySize,
366
-			true
367
-		);
368
-
369
-		return $hash;
370
-	}
371
-
372
-	/**
373
-	 * encrypt private key
374
-	 *
375
-	 * @param string $privateKey
376
-	 * @param string $password
377
-	 * @param string $uid for regular users, empty for system keys
378
-	 * @return false|string
379
-	 */
380
-	public function encryptPrivateKey($privateKey, $password, $uid = '') {
381
-		$cipher = $this->getCipher();
382
-		$hash = $this->generatePasswordHash($password, $cipher, $uid);
383
-		$encryptedKey = $this->symmetricEncryptFileContent(
384
-			$privateKey,
385
-			$hash,
386
-			0,
387
-			0
388
-		);
389
-
390
-		return $encryptedKey;
391
-	}
392
-
393
-	/**
394
-	 * @param string $privateKey
395
-	 * @param string $password
396
-	 * @param string $uid for regular users, empty for system keys
397
-	 * @return false|string
398
-	 */
399
-	public function decryptPrivateKey($privateKey, $password = '', $uid = '') {
400
-		$header = $this->parseHeader($privateKey);
401
-
402
-		if (isset($header['cipher'])) {
403
-			$cipher = $header['cipher'];
404
-		} else {
405
-			$cipher = $this->getLegacyCipher();
406
-		}
407
-
408
-		if (isset($header['keyFormat'])) {
409
-			$keyFormat = $header['keyFormat'];
410
-		} else {
411
-			$keyFormat = self::LEGACY_KEY_FORMAT;
412
-		}
413
-
414
-		if ($keyFormat === 'hash') {
415
-			$password = $this->generatePasswordHash($password, $cipher, $uid);
416
-		}
417
-
418
-		// If we found a header we need to remove it from the key we want to decrypt
419
-		if (!empty($header)) {
420
-			$privateKey = substr($privateKey,
421
-				strpos($privateKey,
422
-					self::HEADER_END) + strlen(self::HEADER_END));
423
-		}
424
-
425
-		$plainKey = $this->symmetricDecryptFileContent(
426
-			$privateKey,
427
-			$password,
428
-			$cipher,
429
-			0
430
-		);
431
-
432
-		if ($this->isValidPrivateKey($plainKey) === false) {
433
-			return false;
434
-		}
435
-
436
-		return $plainKey;
437
-	}
438
-
439
-	/**
440
-	 * check if it is a valid private key
441
-	 *
442
-	 * @param string $plainKey
443
-	 * @return bool
444
-	 */
445
-	protected function isValidPrivateKey($plainKey) {
446
-		$res = openssl_get_privatekey($plainKey);
447
-		// TODO: remove resource check one php7.4 is not longer supported
448
-		if (is_resource($res) || (is_object($res) && get_class($res) === 'OpenSSLAsymmetricKey')) {
449
-			$sslInfo = openssl_pkey_get_details($res);
450
-			if (isset($sslInfo['key'])) {
451
-				return true;
452
-			}
453
-		}
454
-
455
-		return false;
456
-	}
457
-
458
-	/**
459
-	 * @param string $keyFileContents
460
-	 * @param string $passPhrase
461
-	 * @param string $cipher
462
-	 * @param int $version
463
-	 * @param int|string $position
464
-	 * @return string
465
-	 * @throws DecryptionFailedException
466
-	 */
467
-	public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER, $version = 0, $position = 0) {
468
-		if ($keyFileContents == '') {
469
-			return '';
470
-		}
471
-
472
-		$catFile = $this->splitMetaData($keyFileContents, $cipher);
473
-
474
-		if ($catFile['signature'] !== false) {
475
-			try {
476
-				// First try the new format
477
-				$this->checkSignature($catFile['encrypted'], $passPhrase . '_' . $version . '_' . $position, $catFile['signature']);
478
-			} catch (GenericEncryptionException $e) {
479
-				// For compatibility with old files check the version without _
480
-				$this->checkSignature($catFile['encrypted'], $passPhrase . $version . $position, $catFile['signature']);
481
-			}
482
-		}
483
-
484
-		return $this->decrypt($catFile['encrypted'],
485
-			$catFile['iv'],
486
-			$passPhrase,
487
-			$cipher);
488
-	}
489
-
490
-	/**
491
-	 * check for valid signature
492
-	 *
493
-	 * @param string $data
494
-	 * @param string $passPhrase
495
-	 * @param string $expectedSignature
496
-	 * @throws GenericEncryptionException
497
-	 */
498
-	private function checkSignature($data, $passPhrase, $expectedSignature) {
499
-		$enforceSignature = !$this->config->getSystemValue('encryption_skip_signature_check', false);
500
-
501
-		$signature = $this->createSignature($data, $passPhrase);
502
-		$isCorrectHash = hash_equals($expectedSignature, $signature);
503
-
504
-		if (!$isCorrectHash && $enforceSignature) {
505
-			throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature'));
506
-		} elseif (!$isCorrectHash && !$enforceSignature) {
507
-			$this->logger->info("Signature check skipped", ['app' => 'encryption']);
508
-		}
509
-	}
510
-
511
-	/**
512
-	 * create signature
513
-	 *
514
-	 * @param string $data
515
-	 * @param string $passPhrase
516
-	 * @return string
517
-	 */
518
-	private function createSignature($data, $passPhrase) {
519
-		$passPhrase = hash('sha512', $passPhrase . 'a', true);
520
-		return hash_hmac('sha256', $data, $passPhrase);
521
-	}
522
-
523
-
524
-	/**
525
-	 * remove padding
526
-	 *
527
-	 * @param string $padded
528
-	 * @param bool $hasSignature did the block contain a signature, in this case we use a different padding
529
-	 * @return string|false
530
-	 */
531
-	private function removePadding($padded, $hasSignature = false) {
532
-		if ($hasSignature === false && substr($padded, -2) === 'xx') {
533
-			return substr($padded, 0, -2);
534
-		} elseif ($hasSignature === true && substr($padded, -3) === 'xxx') {
535
-			return substr($padded, 0, -3);
536
-		}
537
-		return false;
538
-	}
539
-
540
-	/**
541
-	 * split meta data from encrypted file
542
-	 * Note: for now, we assume that the meta data always start with the iv
543
-	 *       followed by the signature, if available
544
-	 *
545
-	 * @param string $catFile
546
-	 * @param string $cipher
547
-	 * @return array
548
-	 */
549
-	private function splitMetaData($catFile, $cipher) {
550
-		if ($this->hasSignature($catFile, $cipher)) {
551
-			$catFile = $this->removePadding($catFile, true);
552
-			$meta = substr($catFile, -93);
553
-			$iv = substr($meta, strlen('00iv00'), 16);
554
-			$sig = substr($meta, 22 + strlen('00sig00'));
555
-			$encrypted = substr($catFile, 0, -93);
556
-		} else {
557
-			$catFile = $this->removePadding($catFile);
558
-			$meta = substr($catFile, -22);
559
-			$iv = substr($meta, -16);
560
-			$sig = false;
561
-			$encrypted = substr($catFile, 0, -22);
562
-		}
563
-
564
-		return [
565
-			'encrypted' => $encrypted,
566
-			'iv' => $iv,
567
-			'signature' => $sig
568
-		];
569
-	}
570
-
571
-	/**
572
-	 * check if encrypted block is signed
573
-	 *
574
-	 * @param string $catFile
575
-	 * @param string $cipher
576
-	 * @return bool
577
-	 * @throws GenericEncryptionException
578
-	 */
579
-	private function hasSignature($catFile, $cipher) {
580
-		$skipSignatureCheck = $this->config->getSystemValue('encryption_skip_signature_check', false);
581
-
582
-		$meta = substr($catFile, -93);
583
-		$signaturePosition = strpos($meta, '00sig00');
584
-
585
-		// If we no longer support the legacy format then everything needs a signature
586
-		if (!$skipSignatureCheck && !$this->supportLegacy && $signaturePosition === false) {
587
-			throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
588
-		}
589
-
590
-		// enforce signature for the new 'CTR' ciphers
591
-		if (!$skipSignatureCheck && $signaturePosition === false && stripos($cipher, 'ctr') !== false) {
592
-			throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
593
-		}
594
-
595
-		return ($signaturePosition !== false);
596
-	}
597
-
598
-
599
-	/**
600
-	 * @param string $encryptedContent
601
-	 * @param string $iv
602
-	 * @param string $passPhrase
603
-	 * @param string $cipher
604
-	 * @return string
605
-	 * @throws DecryptionFailedException
606
-	 */
607
-	private function decrypt($encryptedContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) {
608
-		$plainContent = openssl_decrypt($encryptedContent,
609
-			$cipher,
610
-			$passPhrase,
611
-			false,
612
-			$iv);
613
-
614
-		if ($plainContent) {
615
-			return $plainContent;
616
-		} else {
617
-			throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
618
-		}
619
-	}
620
-
621
-	/**
622
-	 * @param string $data
623
-	 * @return array
624
-	 */
625
-	protected function parseHeader($data) {
626
-		$result = [];
627
-
628
-		if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
629
-			$endAt = strpos($data, self::HEADER_END);
630
-			$header = substr($data, 0, $endAt + strlen(self::HEADER_END));
631
-
632
-			// +1 not to start with an ':' which would result in empty element at the beginning
633
-			$exploded = explode(':',
634
-				substr($header, strlen(self::HEADER_START) + 1));
635
-
636
-			$element = array_shift($exploded);
637
-
638
-			while ($element !== self::HEADER_END) {
639
-				$result[$element] = array_shift($exploded);
640
-				$element = array_shift($exploded);
641
-			}
642
-		}
643
-
644
-		return $result;
645
-	}
646
-
647
-	/**
648
-	 * generate initialization vector
649
-	 *
650
-	 * @return string
651
-	 * @throws GenericEncryptionException
652
-	 */
653
-	private function generateIv() {
654
-		return random_bytes(16);
655
-	}
656
-
657
-	/**
658
-	 * Generate a cryptographically secure pseudo-random 256-bit ASCII key, used
659
-	 * as file key
660
-	 *
661
-	 * @return string
662
-	 * @throws \Exception
663
-	 */
664
-	public function generateFileKey() {
665
-		return random_bytes(32);
666
-	}
667
-
668
-	/**
669
-	 * @param $encKeyFile
670
-	 * @param $shareKey
671
-	 * @param $privateKey
672
-	 * @return string
673
-	 * @throws MultiKeyDecryptException
674
-	 */
675
-	public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
676
-		if (!$encKeyFile) {
677
-			throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
678
-		}
679
-
680
-		if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
681
-			return $plainContent;
682
-		} else {
683
-			throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
684
-		}
685
-	}
686
-
687
-	/**
688
-	 * @param string $plainContent
689
-	 * @param array $keyFiles
690
-	 * @return array
691
-	 * @throws MultiKeyEncryptException
692
-	 */
693
-	public function multiKeyEncrypt($plainContent, array $keyFiles) {
694
-		// openssl_seal returns false without errors if plaincontent is empty
695
-		// so trigger our own error
696
-		if (empty($plainContent)) {
697
-			throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
698
-		}
699
-
700
-		// Set empty vars to be set by openssl by reference
701
-		$sealed = '';
702
-		$shareKeys = [];
703
-		$mappedShareKeys = [];
704
-
705
-		if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles, 'RC4')) {
706
-			$i = 0;
707
-
708
-			// Ensure each shareKey is labelled with its corresponding key id
709
-			foreach ($keyFiles as $userId => $publicKey) {
710
-				$mappedShareKeys[$userId] = $shareKeys[$i];
711
-				$i++;
712
-			}
713
-
714
-			return [
715
-				'keys' => $mappedShareKeys,
716
-				'data' => $sealed
717
-			];
718
-		} else {
719
-			throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
720
-		}
721
-	}
60
+    public const DEFAULT_CIPHER = 'AES-256-CTR';
61
+    // default cipher from old Nextcloud versions
62
+    public const LEGACY_CIPHER = 'AES-128-CFB';
63
+
64
+    // default key format, old Nextcloud version encrypted the private key directly
65
+    // with the user password
66
+    public const LEGACY_KEY_FORMAT = 'password';
67
+
68
+    public const HEADER_START = 'HBEGIN';
69
+    public const HEADER_END = 'HEND';
70
+
71
+    /** @var ILogger */
72
+    private $logger;
73
+
74
+    /** @var string */
75
+    private $user;
76
+
77
+    /** @var IConfig */
78
+    private $config;
79
+
80
+    /** @var array */
81
+    private $supportedKeyFormats;
82
+
83
+    /** @var IL10N */
84
+    private $l;
85
+
86
+    /** @var array */
87
+    private $supportedCiphersAndKeySize = [
88
+        'AES-256-CTR' => 32,
89
+        'AES-128-CTR' => 16,
90
+        'AES-256-CFB' => 32,
91
+        'AES-128-CFB' => 16,
92
+    ];
93
+
94
+    /** @var bool */
95
+    private $supportLegacy;
96
+
97
+    /**
98
+     * @param ILogger $logger
99
+     * @param IUserSession $userSession
100
+     * @param IConfig $config
101
+     * @param IL10N $l
102
+     */
103
+    public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l) {
104
+        $this->logger = $logger;
105
+        $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
106
+        $this->config = $config;
107
+        $this->l = $l;
108
+        $this->supportedKeyFormats = ['hash', 'password'];
109
+
110
+        $this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
111
+    }
112
+
113
+    /**
114
+     * create new private/public key-pair for user
115
+     *
116
+     * @return array|bool
117
+     */
118
+    public function createKeyPair() {
119
+        $log = $this->logger;
120
+        $res = $this->getOpenSSLPKey();
121
+
122
+        if (!$res) {
123
+            $log->error("Encryption Library couldn't generate users key-pair for {$this->user}",
124
+                ['app' => 'encryption']);
125
+
126
+            if (openssl_error_string()) {
127
+                $log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
128
+                    ['app' => 'encryption']);
129
+            }
130
+        } elseif (openssl_pkey_export($res,
131
+            $privateKey,
132
+            null,
133
+            $this->getOpenSSLConfig())) {
134
+            $keyDetails = openssl_pkey_get_details($res);
135
+            $publicKey = $keyDetails['key'];
136
+
137
+            return [
138
+                'publicKey' => $publicKey,
139
+                'privateKey' => $privateKey
140
+            ];
141
+        }
142
+        $log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user,
143
+            ['app' => 'encryption']);
144
+        if (openssl_error_string()) {
145
+            $log->error('Encryption Library:' . openssl_error_string(),
146
+                ['app' => 'encryption']);
147
+        }
148
+
149
+        return false;
150
+    }
151
+
152
+    /**
153
+     * Generates a new private key
154
+     *
155
+     * @return resource
156
+     */
157
+    public function getOpenSSLPKey() {
158
+        $config = $this->getOpenSSLConfig();
159
+        return openssl_pkey_new($config);
160
+    }
161
+
162
+    /**
163
+     * get openSSL Config
164
+     *
165
+     * @return array
166
+     */
167
+    private function getOpenSSLConfig() {
168
+        $config = ['private_key_bits' => 4096];
169
+        $config = array_merge(
170
+            $config,
171
+            $this->config->getSystemValue('openssl', [])
172
+        );
173
+        return $config;
174
+    }
175
+
176
+    /**
177
+     * @param string $plainContent
178
+     * @param string $passPhrase
179
+     * @param int $version
180
+     * @param int $position
181
+     * @return false|string
182
+     * @throws EncryptionFailedException
183
+     */
184
+    public function symmetricEncryptFileContent($plainContent, $passPhrase, $version, $position) {
185
+        if (!$plainContent) {
186
+            $this->logger->error('Encryption Library, symmetrical encryption failed no content given',
187
+                ['app' => 'encryption']);
188
+            return false;
189
+        }
190
+
191
+        $iv = $this->generateIv();
192
+
193
+        $encryptedContent = $this->encrypt($plainContent,
194
+            $iv,
195
+            $passPhrase,
196
+            $this->getCipher());
197
+
198
+        // Create a signature based on the key as well as the current version
199
+        $sig = $this->createSignature($encryptedContent, $passPhrase.'_'.$version.'_'.$position);
200
+
201
+        // combine content to encrypt the IV identifier and actual IV
202
+        $catFile = $this->concatIV($encryptedContent, $iv);
203
+        $catFile = $this->concatSig($catFile, $sig);
204
+        return $this->addPadding($catFile);
205
+    }
206
+
207
+    /**
208
+     * generate header for encrypted file
209
+     *
210
+     * @param string $keyFormat (can be 'hash' or 'password')
211
+     * @return string
212
+     * @throws \InvalidArgumentException
213
+     */
214
+    public function generateHeader($keyFormat = 'hash') {
215
+        if (in_array($keyFormat, $this->supportedKeyFormats, true) === false) {
216
+            throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported');
217
+        }
218
+
219
+        $cipher = $this->getCipher();
220
+
221
+        $header = self::HEADER_START
222
+            . ':cipher:' . $cipher
223
+            . ':keyFormat:' . $keyFormat
224
+            . ':' . self::HEADER_END;
225
+
226
+        return $header;
227
+    }
228
+
229
+    /**
230
+     * @param string $plainContent
231
+     * @param string $iv
232
+     * @param string $passPhrase
233
+     * @param string $cipher
234
+     * @return string
235
+     * @throws EncryptionFailedException
236
+     */
237
+    private function encrypt($plainContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) {
238
+        $encryptedContent = openssl_encrypt($plainContent,
239
+            $cipher,
240
+            $passPhrase,
241
+            false,
242
+            $iv);
243
+
244
+        if (!$encryptedContent) {
245
+            $error = 'Encryption (symmetric) of content failed';
246
+            $this->logger->error($error . openssl_error_string(),
247
+                ['app' => 'encryption']);
248
+            throw new EncryptionFailedException($error);
249
+        }
250
+
251
+        return $encryptedContent;
252
+    }
253
+
254
+    /**
255
+     * return Cipher either from config.php or the default cipher defined in
256
+     * this class
257
+     *
258
+     * @return string
259
+     */
260
+    public function getCipher() {
261
+        $cipher = $this->config->getSystemValue('cipher', self::DEFAULT_CIPHER);
262
+        if (!isset($this->supportedCiphersAndKeySize[$cipher])) {
263
+            $this->logger->warning(
264
+                    sprintf(
265
+                            'Unsupported cipher (%s) defined in config.php supported. Falling back to %s',
266
+                            $cipher,
267
+                            self::DEFAULT_CIPHER
268
+                    ),
269
+                ['app' => 'encryption']);
270
+            $cipher = self::DEFAULT_CIPHER;
271
+        }
272
+
273
+        // Workaround for OpenSSL 0.9.8. Fallback to an old cipher that should work.
274
+        if (OPENSSL_VERSION_NUMBER < 0x1000101f) {
275
+            if ($cipher === 'AES-256-CTR' || $cipher === 'AES-128-CTR') {
276
+                $cipher = self::LEGACY_CIPHER;
277
+            }
278
+        }
279
+
280
+        return $cipher;
281
+    }
282
+
283
+    /**
284
+     * get key size depending on the cipher
285
+     *
286
+     * @param string $cipher
287
+     * @return int
288
+     * @throws \InvalidArgumentException
289
+     */
290
+    protected function getKeySize($cipher) {
291
+        if (isset($this->supportedCiphersAndKeySize[$cipher])) {
292
+            return $this->supportedCiphersAndKeySize[$cipher];
293
+        }
294
+
295
+        throw new \InvalidArgumentException(
296
+            sprintf(
297
+                    'Unsupported cipher (%s) defined.',
298
+                    $cipher
299
+            )
300
+        );
301
+    }
302
+
303
+    /**
304
+     * get legacy cipher
305
+     *
306
+     * @return string
307
+     */
308
+    public function getLegacyCipher() {
309
+        if (!$this->supportLegacy) {
310
+            throw new ServerNotAvailableException('Legacy cipher is no longer supported!');
311
+        }
312
+
313
+        return self::LEGACY_CIPHER;
314
+    }
315
+
316
+    /**
317
+     * @param string $encryptedContent
318
+     * @param string $iv
319
+     * @return string
320
+     */
321
+    private function concatIV($encryptedContent, $iv) {
322
+        return $encryptedContent . '00iv00' . $iv;
323
+    }
324
+
325
+    /**
326
+     * @param string $encryptedContent
327
+     * @param string $signature
328
+     * @return string
329
+     */
330
+    private function concatSig($encryptedContent, $signature) {
331
+        return $encryptedContent . '00sig00' . $signature;
332
+    }
333
+
334
+    /**
335
+     * Note: This is _NOT_ a padding used for encryption purposes. It is solely
336
+     * used to achieve the PHP stream size. It has _NOTHING_ to do with the
337
+     * encrypted content and is not used in any crypto primitive.
338
+     *
339
+     * @param string $data
340
+     * @return string
341
+     */
342
+    private function addPadding($data) {
343
+        return $data . 'xxx';
344
+    }
345
+
346
+    /**
347
+     * generate password hash used to encrypt the users private key
348
+     *
349
+     * @param string $password
350
+     * @param string $cipher
351
+     * @param string $uid only used for user keys
352
+     * @return string
353
+     */
354
+    protected function generatePasswordHash($password, $cipher, $uid = '') {
355
+        $instanceId = $this->config->getSystemValue('instanceid');
356
+        $instanceSecret = $this->config->getSystemValue('secret');
357
+        $salt = hash('sha256', $uid . $instanceId . $instanceSecret, true);
358
+        $keySize = $this->getKeySize($cipher);
359
+
360
+        $hash = hash_pbkdf2(
361
+            'sha256',
362
+            $password,
363
+            $salt,
364
+            100000,
365
+            $keySize,
366
+            true
367
+        );
368
+
369
+        return $hash;
370
+    }
371
+
372
+    /**
373
+     * encrypt private key
374
+     *
375
+     * @param string $privateKey
376
+     * @param string $password
377
+     * @param string $uid for regular users, empty for system keys
378
+     * @return false|string
379
+     */
380
+    public function encryptPrivateKey($privateKey, $password, $uid = '') {
381
+        $cipher = $this->getCipher();
382
+        $hash = $this->generatePasswordHash($password, $cipher, $uid);
383
+        $encryptedKey = $this->symmetricEncryptFileContent(
384
+            $privateKey,
385
+            $hash,
386
+            0,
387
+            0
388
+        );
389
+
390
+        return $encryptedKey;
391
+    }
392
+
393
+    /**
394
+     * @param string $privateKey
395
+     * @param string $password
396
+     * @param string $uid for regular users, empty for system keys
397
+     * @return false|string
398
+     */
399
+    public function decryptPrivateKey($privateKey, $password = '', $uid = '') {
400
+        $header = $this->parseHeader($privateKey);
401
+
402
+        if (isset($header['cipher'])) {
403
+            $cipher = $header['cipher'];
404
+        } else {
405
+            $cipher = $this->getLegacyCipher();
406
+        }
407
+
408
+        if (isset($header['keyFormat'])) {
409
+            $keyFormat = $header['keyFormat'];
410
+        } else {
411
+            $keyFormat = self::LEGACY_KEY_FORMAT;
412
+        }
413
+
414
+        if ($keyFormat === 'hash') {
415
+            $password = $this->generatePasswordHash($password, $cipher, $uid);
416
+        }
417
+
418
+        // If we found a header we need to remove it from the key we want to decrypt
419
+        if (!empty($header)) {
420
+            $privateKey = substr($privateKey,
421
+                strpos($privateKey,
422
+                    self::HEADER_END) + strlen(self::HEADER_END));
423
+        }
424
+
425
+        $plainKey = $this->symmetricDecryptFileContent(
426
+            $privateKey,
427
+            $password,
428
+            $cipher,
429
+            0
430
+        );
431
+
432
+        if ($this->isValidPrivateKey($plainKey) === false) {
433
+            return false;
434
+        }
435
+
436
+        return $plainKey;
437
+    }
438
+
439
+    /**
440
+     * check if it is a valid private key
441
+     *
442
+     * @param string $plainKey
443
+     * @return bool
444
+     */
445
+    protected function isValidPrivateKey($plainKey) {
446
+        $res = openssl_get_privatekey($plainKey);
447
+        // TODO: remove resource check one php7.4 is not longer supported
448
+        if (is_resource($res) || (is_object($res) && get_class($res) === 'OpenSSLAsymmetricKey')) {
449
+            $sslInfo = openssl_pkey_get_details($res);
450
+            if (isset($sslInfo['key'])) {
451
+                return true;
452
+            }
453
+        }
454
+
455
+        return false;
456
+    }
457
+
458
+    /**
459
+     * @param string $keyFileContents
460
+     * @param string $passPhrase
461
+     * @param string $cipher
462
+     * @param int $version
463
+     * @param int|string $position
464
+     * @return string
465
+     * @throws DecryptionFailedException
466
+     */
467
+    public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER, $version = 0, $position = 0) {
468
+        if ($keyFileContents == '') {
469
+            return '';
470
+        }
471
+
472
+        $catFile = $this->splitMetaData($keyFileContents, $cipher);
473
+
474
+        if ($catFile['signature'] !== false) {
475
+            try {
476
+                // First try the new format
477
+                $this->checkSignature($catFile['encrypted'], $passPhrase . '_' . $version . '_' . $position, $catFile['signature']);
478
+            } catch (GenericEncryptionException $e) {
479
+                // For compatibility with old files check the version without _
480
+                $this->checkSignature($catFile['encrypted'], $passPhrase . $version . $position, $catFile['signature']);
481
+            }
482
+        }
483
+
484
+        return $this->decrypt($catFile['encrypted'],
485
+            $catFile['iv'],
486
+            $passPhrase,
487
+            $cipher);
488
+    }
489
+
490
+    /**
491
+     * check for valid signature
492
+     *
493
+     * @param string $data
494
+     * @param string $passPhrase
495
+     * @param string $expectedSignature
496
+     * @throws GenericEncryptionException
497
+     */
498
+    private function checkSignature($data, $passPhrase, $expectedSignature) {
499
+        $enforceSignature = !$this->config->getSystemValue('encryption_skip_signature_check', false);
500
+
501
+        $signature = $this->createSignature($data, $passPhrase);
502
+        $isCorrectHash = hash_equals($expectedSignature, $signature);
503
+
504
+        if (!$isCorrectHash && $enforceSignature) {
505
+            throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature'));
506
+        } elseif (!$isCorrectHash && !$enforceSignature) {
507
+            $this->logger->info("Signature check skipped", ['app' => 'encryption']);
508
+        }
509
+    }
510
+
511
+    /**
512
+     * create signature
513
+     *
514
+     * @param string $data
515
+     * @param string $passPhrase
516
+     * @return string
517
+     */
518
+    private function createSignature($data, $passPhrase) {
519
+        $passPhrase = hash('sha512', $passPhrase . 'a', true);
520
+        return hash_hmac('sha256', $data, $passPhrase);
521
+    }
522
+
523
+
524
+    /**
525
+     * remove padding
526
+     *
527
+     * @param string $padded
528
+     * @param bool $hasSignature did the block contain a signature, in this case we use a different padding
529
+     * @return string|false
530
+     */
531
+    private function removePadding($padded, $hasSignature = false) {
532
+        if ($hasSignature === false && substr($padded, -2) === 'xx') {
533
+            return substr($padded, 0, -2);
534
+        } elseif ($hasSignature === true && substr($padded, -3) === 'xxx') {
535
+            return substr($padded, 0, -3);
536
+        }
537
+        return false;
538
+    }
539
+
540
+    /**
541
+     * split meta data from encrypted file
542
+     * Note: for now, we assume that the meta data always start with the iv
543
+     *       followed by the signature, if available
544
+     *
545
+     * @param string $catFile
546
+     * @param string $cipher
547
+     * @return array
548
+     */
549
+    private function splitMetaData($catFile, $cipher) {
550
+        if ($this->hasSignature($catFile, $cipher)) {
551
+            $catFile = $this->removePadding($catFile, true);
552
+            $meta = substr($catFile, -93);
553
+            $iv = substr($meta, strlen('00iv00'), 16);
554
+            $sig = substr($meta, 22 + strlen('00sig00'));
555
+            $encrypted = substr($catFile, 0, -93);
556
+        } else {
557
+            $catFile = $this->removePadding($catFile);
558
+            $meta = substr($catFile, -22);
559
+            $iv = substr($meta, -16);
560
+            $sig = false;
561
+            $encrypted = substr($catFile, 0, -22);
562
+        }
563
+
564
+        return [
565
+            'encrypted' => $encrypted,
566
+            'iv' => $iv,
567
+            'signature' => $sig
568
+        ];
569
+    }
570
+
571
+    /**
572
+     * check if encrypted block is signed
573
+     *
574
+     * @param string $catFile
575
+     * @param string $cipher
576
+     * @return bool
577
+     * @throws GenericEncryptionException
578
+     */
579
+    private function hasSignature($catFile, $cipher) {
580
+        $skipSignatureCheck = $this->config->getSystemValue('encryption_skip_signature_check', false);
581
+
582
+        $meta = substr($catFile, -93);
583
+        $signaturePosition = strpos($meta, '00sig00');
584
+
585
+        // If we no longer support the legacy format then everything needs a signature
586
+        if (!$skipSignatureCheck && !$this->supportLegacy && $signaturePosition === false) {
587
+            throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
588
+        }
589
+
590
+        // enforce signature for the new 'CTR' ciphers
591
+        if (!$skipSignatureCheck && $signaturePosition === false && stripos($cipher, 'ctr') !== false) {
592
+            throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
593
+        }
594
+
595
+        return ($signaturePosition !== false);
596
+    }
597
+
598
+
599
+    /**
600
+     * @param string $encryptedContent
601
+     * @param string $iv
602
+     * @param string $passPhrase
603
+     * @param string $cipher
604
+     * @return string
605
+     * @throws DecryptionFailedException
606
+     */
607
+    private function decrypt($encryptedContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) {
608
+        $plainContent = openssl_decrypt($encryptedContent,
609
+            $cipher,
610
+            $passPhrase,
611
+            false,
612
+            $iv);
613
+
614
+        if ($plainContent) {
615
+            return $plainContent;
616
+        } else {
617
+            throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
618
+        }
619
+    }
620
+
621
+    /**
622
+     * @param string $data
623
+     * @return array
624
+     */
625
+    protected function parseHeader($data) {
626
+        $result = [];
627
+
628
+        if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
629
+            $endAt = strpos($data, self::HEADER_END);
630
+            $header = substr($data, 0, $endAt + strlen(self::HEADER_END));
631
+
632
+            // +1 not to start with an ':' which would result in empty element at the beginning
633
+            $exploded = explode(':',
634
+                substr($header, strlen(self::HEADER_START) + 1));
635
+
636
+            $element = array_shift($exploded);
637
+
638
+            while ($element !== self::HEADER_END) {
639
+                $result[$element] = array_shift($exploded);
640
+                $element = array_shift($exploded);
641
+            }
642
+        }
643
+
644
+        return $result;
645
+    }
646
+
647
+    /**
648
+     * generate initialization vector
649
+     *
650
+     * @return string
651
+     * @throws GenericEncryptionException
652
+     */
653
+    private function generateIv() {
654
+        return random_bytes(16);
655
+    }
656
+
657
+    /**
658
+     * Generate a cryptographically secure pseudo-random 256-bit ASCII key, used
659
+     * as file key
660
+     *
661
+     * @return string
662
+     * @throws \Exception
663
+     */
664
+    public function generateFileKey() {
665
+        return random_bytes(32);
666
+    }
667
+
668
+    /**
669
+     * @param $encKeyFile
670
+     * @param $shareKey
671
+     * @param $privateKey
672
+     * @return string
673
+     * @throws MultiKeyDecryptException
674
+     */
675
+    public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
676
+        if (!$encKeyFile) {
677
+            throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
678
+        }
679
+
680
+        if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
681
+            return $plainContent;
682
+        } else {
683
+            throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
684
+        }
685
+    }
686
+
687
+    /**
688
+     * @param string $plainContent
689
+     * @param array $keyFiles
690
+     * @return array
691
+     * @throws MultiKeyEncryptException
692
+     */
693
+    public function multiKeyEncrypt($plainContent, array $keyFiles) {
694
+        // openssl_seal returns false without errors if plaincontent is empty
695
+        // so trigger our own error
696
+        if (empty($plainContent)) {
697
+            throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
698
+        }
699
+
700
+        // Set empty vars to be set by openssl by reference
701
+        $sealed = '';
702
+        $shareKeys = [];
703
+        $mappedShareKeys = [];
704
+
705
+        if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles, 'RC4')) {
706
+            $i = 0;
707
+
708
+            // Ensure each shareKey is labelled with its corresponding key id
709
+            foreach ($keyFiles as $userId => $publicKey) {
710
+                $mappedShareKeys[$userId] = $shareKeys[$i];
711
+                $i++;
712
+            }
713
+
714
+            return [
715
+                'keys' => $mappedShareKeys,
716
+                'data' => $sealed
717
+            ];
718
+        } else {
719
+            throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
720
+        }
721
+    }
722 722
 }
Please login to merge, or discard this patch.
Spacing   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
 				['app' => 'encryption']);
125 125
 
126 126
 			if (openssl_error_string()) {
127
-				$log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
127
+				$log->error('Encryption library openssl_pkey_new() fails: '.openssl_error_string(),
128 128
 					['app' => 'encryption']);
129 129
 			}
130 130
 		} elseif (openssl_pkey_export($res,
@@ -139,10 +139,10 @@  discard block
 block discarded – undo
139 139
 				'privateKey' => $privateKey
140 140
 			];
141 141
 		}
142
-		$log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user,
142
+		$log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.'.$this->user,
143 143
 			['app' => 'encryption']);
144 144
 		if (openssl_error_string()) {
145
-			$log->error('Encryption Library:' . openssl_error_string(),
145
+			$log->error('Encryption Library:'.openssl_error_string(),
146 146
 				['app' => 'encryption']);
147 147
 		}
148 148
 
@@ -213,15 +213,15 @@  discard block
 block discarded – undo
213 213
 	 */
214 214
 	public function generateHeader($keyFormat = 'hash') {
215 215
 		if (in_array($keyFormat, $this->supportedKeyFormats, true) === false) {
216
-			throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported');
216
+			throw new \InvalidArgumentException('key format "'.$keyFormat.'" is not supported');
217 217
 		}
218 218
 
219 219
 		$cipher = $this->getCipher();
220 220
 
221 221
 		$header = self::HEADER_START
222
-			. ':cipher:' . $cipher
223
-			. ':keyFormat:' . $keyFormat
224
-			. ':' . self::HEADER_END;
222
+			. ':cipher:'.$cipher
223
+			. ':keyFormat:'.$keyFormat
224
+			. ':'.self::HEADER_END;
225 225
 
226 226
 		return $header;
227 227
 	}
@@ -243,7 +243,7 @@  discard block
 block discarded – undo
243 243
 
244 244
 		if (!$encryptedContent) {
245 245
 			$error = 'Encryption (symmetric) of content failed';
246
-			$this->logger->error($error . openssl_error_string(),
246
+			$this->logger->error($error.openssl_error_string(),
247 247
 				['app' => 'encryption']);
248 248
 			throw new EncryptionFailedException($error);
249 249
 		}
@@ -319,7 +319,7 @@  discard block
 block discarded – undo
319 319
 	 * @return string
320 320
 	 */
321 321
 	private function concatIV($encryptedContent, $iv) {
322
-		return $encryptedContent . '00iv00' . $iv;
322
+		return $encryptedContent.'00iv00'.$iv;
323 323
 	}
324 324
 
325 325
 	/**
@@ -328,7 +328,7 @@  discard block
 block discarded – undo
328 328
 	 * @return string
329 329
 	 */
330 330
 	private function concatSig($encryptedContent, $signature) {
331
-		return $encryptedContent . '00sig00' . $signature;
331
+		return $encryptedContent.'00sig00'.$signature;
332 332
 	}
333 333
 
334 334
 	/**
@@ -340,7 +340,7 @@  discard block
 block discarded – undo
340 340
 	 * @return string
341 341
 	 */
342 342
 	private function addPadding($data) {
343
-		return $data . 'xxx';
343
+		return $data.'xxx';
344 344
 	}
345 345
 
346 346
 	/**
@@ -354,7 +354,7 @@  discard block
 block discarded – undo
354 354
 	protected function generatePasswordHash($password, $cipher, $uid = '') {
355 355
 		$instanceId = $this->config->getSystemValue('instanceid');
356 356
 		$instanceSecret = $this->config->getSystemValue('secret');
357
-		$salt = hash('sha256', $uid . $instanceId . $instanceSecret, true);
357
+		$salt = hash('sha256', $uid.$instanceId.$instanceSecret, true);
358 358
 		$keySize = $this->getKeySize($cipher);
359 359
 
360 360
 		$hash = hash_pbkdf2(
@@ -474,10 +474,10 @@  discard block
 block discarded – undo
474 474
 		if ($catFile['signature'] !== false) {
475 475
 			try {
476 476
 				// First try the new format
477
-				$this->checkSignature($catFile['encrypted'], $passPhrase . '_' . $version . '_' . $position, $catFile['signature']);
477
+				$this->checkSignature($catFile['encrypted'], $passPhrase.'_'.$version.'_'.$position, $catFile['signature']);
478 478
 			} catch (GenericEncryptionException $e) {
479 479
 				// For compatibility with old files check the version without _
480
-				$this->checkSignature($catFile['encrypted'], $passPhrase . $version . $position, $catFile['signature']);
480
+				$this->checkSignature($catFile['encrypted'], $passPhrase.$version.$position, $catFile['signature']);
481 481
 			}
482 482
 		}
483 483
 
@@ -516,7 +516,7 @@  discard block
 block discarded – undo
516 516
 	 * @return string
517 517
 	 */
518 518
 	private function createSignature($data, $passPhrase) {
519
-		$passPhrase = hash('sha512', $passPhrase . 'a', true);
519
+		$passPhrase = hash('sha512', $passPhrase.'a', true);
520 520
 		return hash_hmac('sha256', $data, $passPhrase);
521 521
 	}
522 522
 
@@ -614,7 +614,7 @@  discard block
 block discarded – undo
614 614
 		if ($plainContent) {
615 615
 			return $plainContent;
616 616
 		} else {
617
-			throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
617
+			throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: '.openssl_error_string());
618 618
 		}
619 619
 	}
620 620
 
@@ -680,7 +680,7 @@  discard block
 block discarded – undo
680 680
 		if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
681 681
 			return $plainContent;
682 682
 		} else {
683
-			throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
683
+			throw new MultiKeyDecryptException('multikeydecrypt with share key failed:'.openssl_error_string());
684 684
 		}
685 685
 	}
686 686
 
@@ -716,7 +716,7 @@  discard block
 block discarded – undo
716 716
 				'data' => $sealed
717 717
 			];
718 718
 		} else {
719
-			throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
719
+			throw new MultiKeyEncryptException('multikeyencryption failed '.openssl_error_string());
720 720
 		}
721 721
 	}
722 722
 }
Please login to merge, or discard this patch.