3 * @copyright Copyright (C) 2010-2021, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Model\Storage;
25 use Friendica\Core\Config\IConfig;
26 use Friendica\Core\L10n;
27 use Friendica\Util\Strings;
30 * Filesystem based storage backend
32 * This class manage data on filesystem.
33 * Base folder for storage is set in storage.filesystem_path.
34 * Best would be for storage folder to be outside webserver folder, we are using a
35 * folder relative to code tree root as default to ease things for users in shared hostings.
36 * Each new resource gets a value as reference and is saved in a
37 * folder tree stucture created from that value.
39 class Filesystem implements ISelectableStorage
41 const NAME = 'Filesystem';
43 // Default base folder
44 const DEFAULT_BASE_FOLDER = 'storage';
56 * Filesystem constructor.
58 * @param IConfig $config
61 public function __construct(IConfig $config, L10n $l10n)
63 $this->config = $config;
66 $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
67 $this->basePath = rtrim($path, '/');
71 * Split data ref and return file path
73 * @param string $reference Data reference
77 private function pathForRef(string $reference): string
79 $fold1 = substr($reference, 0, 2);
80 $fold2 = substr($reference, 2, 2);
81 $file = substr($reference, 4);
83 return implode('/', [$this->basePath, $fold1, $fold2, $file]);
88 * Create directory tree to store file, with .htaccess and index.html files
90 * @param string $file Path and filename
92 * @throws StorageException
94 private function createFoldersForFile(string $file)
96 $path = dirname($file);
99 if (!mkdir($path, 0770, true)) {
100 throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
104 while ($path !== $this->basePath) {
105 if (!is_file($path . '/index.html')) {
106 file_put_contents($path . '/index.html', '');
108 chmod($path . '/index.html', 0660);
110 $path = dirname($path);
112 if (!is_file($path . '/index.html')) {
113 file_put_contents($path . '/index.html', '');
114 chmod($path . '/index.html', 0660);
121 public function get(string $reference): string
123 $file = $this->pathForRef($reference);
124 if (!is_file($file)) {
125 throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
128 $result = file_get_contents($file);
130 // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
131 if ($result === false) {
132 throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
141 public function put(string $data, string $reference = ''): string
143 if ($reference === '') {
145 $reference = Strings::getRandomHex();
146 } catch (Exception $exception) {
147 throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
150 $file = $this->pathForRef($reference);
152 $this->createFoldersForFile($file);
154 $result = file_put_contents($file, $data);
156 // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
157 if ($result === false) {
158 throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
168 public function delete(string $reference)
170 $file = $this->pathForRef($reference);
171 if (!is_file($file)) {
172 throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
175 if (!unlink($file)) {
176 throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
183 public function getOptions(): array
188 $this->l10n->t('Storage base path'),
190 $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
198 public function saveOptions(array $data): array
200 $storagePath = $data['storagepath'] ?? '';
201 if ($storagePath === '' || !is_dir($storagePath)) {
203 'storagepath' => $this->l10n->t('Enter a valid existing folder')
206 $this->config->set('storage', 'filesystem_path', $storagePath);
207 $this->basePath = $storagePath;
214 public static function getName(): string
219 public function __toString()
221 return self::getName();