initial commit

This commit is contained in:
Milos Stojanovic
2019-06-13 18:52:40 +02:00
commit 261607e1d3
160 changed files with 41704 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Archiver\Adapters;
use Filegator\Services\Archiver\ArchiverInterface;
use Filegator\Services\Service;
use Filegator\Services\Storage\Filesystem as Storage;
use Filegator\Services\Tmpfs\TmpfsInterface;
use League\Flysystem\Config as Flyconfig;
use League\Flysystem\Filesystem as Flysystem;
use League\Flysystem\ZipArchive\ZipArchiveAdapter;
class ZipArchiver implements Service, ArchiverInterface
{
protected $archive;
protected $storage;
protected $tmpfs;
protected $uniqid;
protected $tmp_files = [];
public function __construct(TmpfsInterface $tmpfs)
{
$this->tmpfs = $tmpfs;
}
public function init(array $config = [])
{
}
public function createArchive(Storage $storage): string
{
$this->uniqid = uniqid();
$this->archive = new Flysystem(
new ZipAdapter($this->tmpfs->getFileLocation($this->uniqid))
);
$this->storage = $storage;
return $this->uniqid;
}
public function addDirectoryFromStorage(string $path)
{
$content = $this->storage->getDirectoryCollection($path, true);
$this->archive->createDir($path);
foreach ($content->all() as $item) {
if ($item['type'] == 'dir') {
$this->archive->createDir($item['path']);
}
if ($item['type'] == 'file') {
$this->addFileFromStorage($item['path']);
}
}
}
public function addFileFromStorage(string $path)
{
$file_uniqid = uniqid();
$file = $this->storage->readStream($path);
$this->tmpfs->write($file_uniqid, $file['stream']);
$this->archive->write($path, $this->tmpfs->getFileLocation($file_uniqid));
$this->tmp_files[] = $file_uniqid;
}
public function uncompress(string $source, string $destination, Storage $storage)
{
$name = uniqid().'.zip';
$remote_archive = $storage->readStream($source);
$this->tmpfs->write($name, $remote_archive['stream']);
$archive = new Flysystem(
new ZipAdapter($this->tmpfs->getFileLocation($name))
);
$contents = $archive->listContents('/', true);
foreach ($contents as $item) {
$stream = $archive->readStream($item['path']);
if ($item['type'] == 'dir') {
$storage->createDir($destination, $item['path'], $stream);
}
if ($item['type'] == 'file') {
$storage->store($destination.'/'.$item['dirname'], $item['basename'], $stream);
}
if (is_resource($stream)) {
fclose($stream);
}
}
$this->tmpfs->remove($name);
}
public function closeArchive()
{
$this->archive->getAdapter()->getArchive()->close();
foreach ($this->tmp_files as $file) {
$this->tmpfs->remove($file);
}
}
public function storeArchive($destination, $name)
{
$this->closeArchive();
$file = $this->tmpfs->readStream($this->uniqid);
$this->storage->store($destination, $name, $file['stream']);
fclose($file['stream']);
$this->tmpfs->remove($this->uniqid);
}
}
class ZipAdapter extends ZipArchiveAdapter
{
public function write($path, $contents, Flyconfig $config)
{
$location = $this->applyPathPrefix($path);
// using addFile instead of addFromString
// is more memory efficient
$this->archive->addFile($contents, $location);
return compact('path', 'contents');
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Archiver;
use Filegator\Services\Storage\Filesystem;
interface ArchiverInterface
{
public function createArchive(Filesystem $storage): string;
public function uncompress(string $source, string $destination, Filesystem $storage);
public function addDirectoryFromStorage(string $path);
public function addFileFromStorage(string $path);
public function closeArchive();
public function storeArchive($destination, $name);
}

View File

@@ -0,0 +1,185 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Auth\Adapters;
use Dibi\Connection;
use Dibi\Row;
use Filegator\Services\Auth\AuthInterface;
use Filegator\Services\Auth\User;
use Filegator\Services\Auth\UsersCollection;
use Filegator\Services\Service;
use Filegator\Services\Session\SessionStorageInterface as Session;
use Filegator\Utils\PasswordHash;
class Database implements Service, AuthInterface
{
use PasswordHash;
const SESSION_KEY = 'database_auth';
const GUEST_USERNAME = 'guest';
protected $session;
public function __construct(Session $session)
{
$this->session = $session;
}
public function init(array $config = [])
{
$this->conn = new Connection($config);
}
public function getConnection()
{
return $this->conn;
}
public function user(): ?User
{
return $this->session ? $this->session->get(self::SESSION_KEY, null) : null;
}
public function authenticate($username, $password): bool
{
$ret = $this->getConnection()
->fetch('SELECT * FROM users WHERE username = ?', $username)
;
if ($ret && $this->verifyPassword($password, $ret->password)) {
$user = $this->mapToUserObject($ret);
$this->store($user);
return true;
}
return false;
}
public function forget()
{
return $this->session->invalidate();
}
public function store(User $user)
{
return $this->session->set(self::SESSION_KEY, $user);
}
public function update($username, User $user, $password = ''): User
{
if (! $this->find($username)) {
throw new \Exception('User not found');
}
if ($username != $user->getUsername() && $this->find($user->getUsername())) {
throw new \Exception('Username already taken');
}
$this->getConnection()->query('UPDATE users SET', [
'username' => $user->getUsername(),
'name' => $user->getName(),
'homedir' => $user->getHomeDir(),
'permissions' => $user->getPermissions(true),
'role' => $user->getRole(),
], 'WHERE username = ?', $username);
if ($password) {
$this->getConnection()->query('UPDATE users SET', [
'password' => $this->hashPassword($password),
], 'WHERE username = ?', $username);
}
return $this->find($user->getUsername());
}
public function add(User $user, $password): User
{
if ($this->find($user->getUsername())) {
throw new \Exception('Username already taken');
}
$this->getConnection()->query('INSERT INTO users', [
'username' => $user->getUsername(),
'name' => $user->getName(),
'role' => $user->getRole(),
'homedir' => $user->getHomeDir(),
'permissions' => $user->getPermissions(true),
'password' => $this->hashPassword($password),
]);
return $this->find($user->getUsername());
}
public function delete(User $user)
{
if (! $this->find($user->getUsername())) {
throw new \Exception('User not found');
}
$this->getConnection()->query('DELETE FROM users WHERE username = ?', $user->getUsername());
return true;
}
public function find($username): ?User
{
$row = $this->getConnection()
->fetch('SELECT * FROM users WHERE username = ?', $username)
;
if ($row) {
return $this->mapToUserObject($row);
}
return null;
}
public function getGuest(): User
{
$guest = $this->find(self::GUEST_USERNAME);
if (! $guest || ! $guest->isGuest()) {
throw new \Exception('No guest account');
}
return $guest;
}
public function allUsers(): UsersCollection
{
$users = new UsersCollection();
$rows = $this->getConnection()
->fetchAll('SELECT * FROM users')
;
foreach ($rows as $user) {
$users->addUser($this->mapToUserObject($user));
}
return $users;
}
protected function mapToUserObject(Row $user): User
{
$new = new User();
$new->setRole($user->role);
$new->setHomedir($user->homedir);
$new->setPermissions($user->permissions, true);
$new->setUsername($user->username);
$new->setName($user->name);
return $new;
}
}

View File

@@ -0,0 +1,201 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Auth\Adapters;
use Filegator\Services\Auth\AuthInterface;
use Filegator\Services\Auth\User;
use Filegator\Services\Auth\UsersCollection;
use Filegator\Services\Service;
use Filegator\Services\Session\SessionStorageInterface as Session;
use Filegator\Utils\PasswordHash;
class JsonFile implements Service, AuthInterface
{
use PasswordHash;
const SESSION_KEY = 'json_auth';
const GUEST_USERNAME = 'guest';
protected $session;
protected $file;
public function __construct(Session $session)
{
$this->session = $session;
}
public function init(array $config = [])
{
if (! file_exists($config['file'])) {
copy($config['file'].'.blank', $config['file']);
}
$this->file = $config['file'];
}
public function user(): ?User
{
return $this->session ? $this->session->get(self::SESSION_KEY, null) : null;
}
public function authenticate($username, $password): bool
{
$all_users = $this->getUsers();
foreach ($all_users as &$u) {
if ($u['username'] == $username && $this->verifyPassword($password, $u['password'])) {
$user = $this->mapToUserObject($u);
$this->store($user);
return true;
}
}
return false;
}
public function forget()
{
return $this->session->invalidate();
}
public function store(User $user)
{
return $this->session->set(self::SESSION_KEY, $user);
}
public function update($username, User $user, $password = ''): User
{
$all_users = $this->getUsers();
if ($username != $user->getUsername() && $this->find($user->getUsername())) {
throw new \Exception('Username already taken');
}
foreach ($all_users as &$u) {
if ($u['username'] == $username) {
$u['username'] = $user->getUsername();
$u['name'] = $user->getName();
$u['role'] = $user->getRole();
$u['homedir'] = $user->getHomeDir();
$u['permissions'] = $user->getPermissions(true);
if ($password) {
$u['password'] = $this->hashPassword($password);
}
$this->saveUsers($all_users);
return $this->find($user->getUsername());
}
}
throw new \Exception('User not found');
}
public function add(User $user, $password): User
{
if ($this->find($user->getUsername())) {
throw new \Exception('Username already taken');
}
$all_users = $this->getUsers();
$all_users[] = [
'username' => $user->getUsername(),
'name' => $user->getName(),
'role' => $user->getRole(),
'homedir' => $user->getHomeDir(),
'permissions' => $user->getPermissions(true),
'password' => $this->hashPassword($password),
];
$this->saveUsers($all_users);
return $this->find($user->getUsername());
}
public function delete(User $user)
{
$all_users = $this->getUsers();
foreach ($all_users as $key => $u) {
if ($u['username'] == $user->getUsername()) {
unset($all_users[$key]);
$this->saveUsers($all_users);
return true;
}
}
throw new \Exception('User not found');
}
public function find($username): ?User
{
foreach ($this->getUsers() as $user) {
if ($user['username'] == $username) {
return $this->mapToUserObject($user);
}
}
return null;
}
public function getGuest(): User
{
$guest = $this->find(self::GUEST_USERNAME);
if (! $guest || ! $guest->isGuest()) {
throw new \Exception('No guest account');
}
return $guest;
}
public function allUsers(): UsersCollection
{
$users = new UsersCollection();
foreach ($this->getUsers() as $user) {
$users->addUser($this->mapToUserObject($user));
}
return $users;
}
protected function mapToUserObject(array $user): User
{
$new = new User();
$new->setUsername($user['username']);
$new->setName($user['name']);
$new->setRole($user['role']);
$new->setHomedir($user['homedir']);
$new->setPermissions($user['permissions'], true);
return $new;
}
protected function getUsers(): array
{
$users = json_decode(file_get_contents($this->file), true);
return is_array($users) ? $users : [];
}
protected function saveUsers(array $users)
{
return file_put_contents($this->file, json_encode($users), LOCK_EX);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Auth;
interface AuthInterface
{
public function user(): ?User;
public function authenticate($username, $password): bool;
public function forget();
public function find($username): ?User;
public function store(User $user);
public function update($username, User $user, $password = ''): User;
public function add(User $user, $password): User;
public function delete(User $user);
public function getGuest(): User;
public function allUsers(): UsersCollection;
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Auth;
class User implements \JsonSerializable
{
protected $role = 'guest';
protected $permissions = [];
protected $username = '';
protected $homedir = '';
protected $name = '';
protected $available_roles = ['guest', 'user', 'admin'];
protected $available_permissions = ['read', 'write', 'upload', 'download', 'batchdownload', 'zip'];
public function __construct()
{
}
public function isGuest(): bool
{
return 'guest' == $this->role;
}
public function isUser(): bool
{
return 'user' == $this->role;
}
public function isAdmin(): bool
{
return 'admin' == $this->role;
}
public function hasRole($check): bool
{
if (is_array($check)) {
return in_array($this->getRole(), $check);
}
return $this->getRole() == $check;
}
public function getRole(): string
{
return $this->role;
}
public function hasPermissions($check): bool
{
if (empty($check)) {
return true;
}
if (is_array($check)) {
return count(array_intersect($check, $this->getPermissions())) == count($check);
}
return in_array($check, $this->getPermissions());
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
public function setUsername(string $username)
{
$this->username = $username;
}
public function getUsername(): string
{
return $this->username;
}
public function setHomedir(string $homedir)
{
$this->homedir = $homedir;
}
public function getHomeDir(): string
{
return $this->homedir;
}
public function setRole(string $role)
{
$this->checkValidRole($role);
$this->role = $role;
}
public function setPermissions($permissions, $encoded = false)
{
if ($encoded) {
$permissions = explode('|', $permissions);
}
$this->checkValidPermissions($permissions);
$this->permissions = $permissions;
}
public function getPermissions($encoded = false)
{
return $encoded ? implode('|', $this->permissions) : $this->permissions;
}
public function jsonSerialize()
{
return [
'role' => $this->getRole(),
'permissions' => $this->getPermissions(),
'homedir' => $this->getHomeDir(),
'username' => $this->getUsername(),
'name' => $this->getName(),
];
}
protected function checkValidRole($role)
{
if (! in_array($role, $this->available_roles)) {
throw new \Exception("User role {$role} does not exists.");
}
return true;
}
protected function checkValidPermissions(array $permissions)
{
foreach ($permissions as $permission) {
if ($permission && ! in_array($permission, $this->available_permissions)) {
throw new \Exception("Permission {$permission} does not exists.");
}
}
return true;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Auth;
use Filegator\Utils\Collection;
class UsersCollection implements \JsonSerializable
{
use Collection;
public function addUser(User $user)
{
$this->add($user);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Cors;
use Filegator\Kernel\Request;
use Filegator\Kernel\Response;
use Filegator\Services\Service;
/**
* @codeCoverageIgnore
*/
class Cors implements Service
{
protected $request;
protected $response;
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
public function init(array $config = [])
{
if ($config['enabled'] !== true) {
return;
}
$this->response->headers->set('Access-Control-Allow-Origin', $this->request->headers->get('Origin'));
$this->response->headers->set('Access-Control-Allow-Credentials', 'true');
$this->response->headers->set('Access-Control-Expose-Headers', 'x-csrf-token');
if ($this->request->server->get('REQUEST_METHOD') == 'OPTIONS') {
$this->response->headers->set('Access-Control-Allow-Headers', 'content-type, x-csrf-token');
$this->response->send();
die;
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Logger\Adapters;
use Filegator\Services\Logger\LoggerInterface;
use Filegator\Services\Service;
use Monolog\ErrorHandler;
use Monolog\Logger;
class MonoLogger implements Service, LoggerInterface
{
protected $logger;
public function init(array $config = [])
{
$this->logger = new Logger('default');
foreach ($config['monolog_handlers'] as $handler) {
$this->logger->pushHandler($handler());
}
$handler = new ErrorHandler($this->logger);
$handler->registerErrorHandler([], true);
$handler->registerFatalHandler();
}
public function log(string $message, string $level = Logger::INFO)
{
$this->logger->log($level, $message);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Logger;
interface LoggerInterface
{
public function log(string $message, string $level);
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Router;
use FastRoute;
use Filegator\Container\Container;
use Filegator\Kernel\Request;
use Filegator\Services\Auth\AuthInterface;
use Filegator\Services\Service;
/**
* Uses unique query parameter to pass the route info
* Does not require rewrite rules, .htaccess or similar.
*
* Example routes:
*
* http://example.com/?r=/some/route&param1=val1&param2=val2
* http://example.com/?r=/user/{user_id}&param1=val1
*/
class Router implements Service
{
protected $request;
protected $auth;
protected $container;
public function __construct(Request $request, AuthInterface $auth, Container $container)
{
$this->request = $request;
$this->container = $container;
$this->user = $auth->user() ?: $auth->getGuest();
}
public function init(array $config = [])
{
$uri = '/';
$http_method = $this->request->getMethod();
if ($r = $this->request->query->get($config['query_param'])) {
$this->request->query->remove($config['query_param']);
$uri = rawurldecode($r);
}
$routes = require $config['routes_file'];
$dispatcher = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) use ($routes) {
if ($routes && ! empty($routes)) {
foreach ($routes as $params) {
if ($this->user->hasRole($params['roles']) && $this->user->hasPermissions($params['permissions'])) {
$r->addRoute($params['route'][0], $params['route'][1], $params['route'][2]);
}
}
}
});
$routeInfo = $dispatcher->dispatch($http_method, $uri);
$controller = '\Filegator\Controllers\ErrorController';
$action = 'notFound';
$params = [];
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::FOUND:
$handler = explode('@', $routeInfo[1]);
$controller = $handler[0];
$action = $handler[1];
$params = $routeInfo[2];
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$action = 'methodNotAllowed';
break;
}
$this->container->call([$controller, $action], $params);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Security;
use Filegator\Kernel\Request;
use Filegator\Kernel\Response;
use Filegator\Services\Service;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
/**
* @codeCoverageIgnore
*/
class Security implements Service
{
protected $request;
protected $response;
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
public function init(array $config = [])
{
if ($config['csrf_protection']) {
$http_method = $this->request->getMethod();
$csrfManager = new CsrfTokenManager();
if (in_array($http_method, ['GET', 'HEAD', 'OPTIONS'])) {
$this->response->headers->set('X-CSRF-Token', $csrfManager->getToken('protection'));
} else {
$token = new CsrfToken('protection', $this->request->headers->get('X-CSRF-Token'));
if (! $csrfManager->isTokenValid($token)) {
throw new \Exception('Csrf token not valid');
}
}
}
if (! empty($config['ip_whitelist'])) {
$pass = false;
foreach ($config['ip_whitelist'] as $ip) {
if ($this->request->getClientIp() == $ip) {
$pass = true;
}
}
if (! $pass) {
$this->response->setStatusCode(403);
$this->response->send();
die;
}
}
if (! empty($config['ip_blacklist'])) {
$pass = true;
foreach ($config['ip_blacklist'] as $ip) {
if ($this->request->getClientIp() == $ip) {
$pass = false;
}
}
if (! $pass) {
$this->response->setStatusCode(403);
$this->response->send();
die;
}
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services;
interface Service
{
public function init(array $config = []);
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Session\Adapters;
use Filegator\Kernel\Request;
use Filegator\Services\Service;
use Filegator\Services\Session\Session;
use Filegator\Services\Session\SessionStorageInterface;
class SessionStorage implements Service, SessionStorageInterface
{
protected $request;
protected $config;
public function __construct(Request $request)
{
$this->request = $request;
}
public function init(array $config = [])
{
// we don't have a previous session attached
if (! $this->getSession()) {
$handler = $config['available'][$config['session_handler']];
$session = new Session($handler());
$session->setName('filegator');
$this->setSession($session);
}
}
public function save()
{
$this->getSession()->save();
}
public function set(string $key, $data)
{
return $this->getSession()->set($key, $data);
}
public function get(string $key, $default = null)
{
return $this->getSession() ? $this->getSession()->get($key, $default) : $default;
}
public function invalidate()
{
if (! $this->getSession()->isStarted()) {
$this->getSession()->start();
}
$this->getSession()->invalidate();
}
private function setSession(Session $session)
{
return $this->request->setSession($session);
}
private function getSession(): ?Session
{
return $this->request->getSession();
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Session;
use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
class Session extends SymfonySession
{
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Session;
interface SessionStorageInterface
{
public function set(string $key, $data);
public function get(string $key, $default = null);
public function invalidate();
public function save();
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Storage;
use Filegator\Utils\Collection;
class DirectoryCollection implements \JsonSerializable
{
use Collection;
protected $location;
public function __construct($location)
{
$this->location = $location;
}
public function addFile(string $type, string $path, string $name, int $size, int $timestamp)
{
if (! in_array($type, ['dir', 'file', 'back'])) {
throw new \Exception('Invalid file type.');
}
$this->add([
'type' => $type,
'path' => $path,
'name' => $name,
'size' => $size,
'time' => $timestamp,
]);
}
public function resetTimestamps($timestamp = 0)
{
foreach ($this->items as &$item) {
$item['time'] = $timestamp;
}
}
public function jsonSerialize()
{
$this->sortByValue('type');
return [
'location' => $this->location,
'files' => $this->items,
];
}
}

View File

@@ -0,0 +1,287 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Storage;
use Filegator\Services\Service;
use League\Flysystem\Filesystem as Flysystem;
class Filesystem implements Service
{
protected $separator;
protected $storage;
protected $path_prefix;
public function init(array $config = [])
{
$this->separator = $config['separator'];
$this->path_prefix = $this->separator;
$adapter = $config['adapters'][$config['filesystem_adapter']];
$config = isset($config['config']) ? $config['config'] : [];
$this->storage = new Flysystem($adapter(), $config);
}
public function createDir(string $path, string $name)
{
$destination = $this->joinPaths($this->applyPathPrefix($path), $name);
while (! empty($this->storage->listContents($destination, true))) {
$destination = $this->upcountName($destination);
}
return $this->storage->createDir($destination);
}
public function createFile(string $path, string $name)
{
$destination = $this->joinPaths($this->applyPathPrefix($path), $name);
while ($this->storage->has($destination)) {
$destination = $this->upcountName($destination);
}
$this->storage->put($destination, '');
}
public function fileExists(string $path)
{
$path = $this->applyPathPrefix($path);
return $this->storage->has($path);
}
public function isDir(string $path)
{
$path = $this->applyPathPrefix($path);
return $this->storage->getSize($path) === false;
}
public function copyFile(string $source, string $destination)
{
$source = $this->applyPathPrefix($source);
$destination = $this->joinPaths($this->applyPathPrefix($destination), $this->getBaseName($source));
while ($this->storage->has($destination)) {
$destination = $this->upcountName($destination);
}
return $this->storage->copy($source, $destination);
}
public function copyDir(string $source, string $destination)
{
$source = $this->applyPathPrefix($this->addSeparators($source));
$destination = $this->applyPathPrefix($this->addSeparators($destination));
$source_dir = $this->getBaseName($source);
$real_destination = $this->joinPaths($destination, $source_dir);
while (! empty($this->storage->listContents($real_destination, true))) {
$real_destination = $this->upcountName($real_destination);
}
$contents = $this->storage->listContents($source, true);
if (empty($contents)) {
$this->storage->createDir($real_destination);
}
foreach ($contents as $file) {
$source_path = $this->separator.ltrim($file['path'], $this->separator);
$path = substr($source_path, strlen($source), strlen($source_path));
if ($file['type'] == 'dir') {
$this->storage->createDir($this->joinPaths($real_destination, $path));
continue;
}
if ($file['type'] == 'file') {
$this->storage->copy($file['path'], $this->joinPaths($real_destination, $path));
}
}
}
public function deleteDir(string $path)
{
return $this->storage->deleteDir($this->applyPathPrefix($path));
}
public function deleteFile(string $path)
{
return $this->storage->delete($this->applyPathPrefix($path));
}
public function readStream(string $path): array
{
if ($this->isDir($path)) {
throw new \Exception('Cannot stream directory');
}
$path = $this->applyPathPrefix($path);
return [
'filename' => $this->getBaseName($path),
'stream' => $this->storage->readStream($path),
];
}
public function move(string $from, string $to): bool
{
$from = $this->applyPathPrefix($from);
$to = $this->applyPathPrefix($to);
while ($this->storage->has($to)) {
$to = $this->upcountName($to);
}
return $this->storage->rename($from, $to);
}
public function rename(string $destination, string $from, string $to): bool
{
$from = $this->joinPaths($this->applyPathPrefix($destination), $from);
$to = $this->joinPaths($this->applyPathPrefix($destination), $to);
while ($this->storage->has($to)) {
$to = $this->upcountName($to);
}
return $this->storage->rename($from, $to);
}
public function store(string $path, string $name, $resource): bool
{
$destination = $this->joinPaths($this->applyPathPrefix($path), $name);
while ($this->storage->has($destination)) {
$destination = $this->upcountName($destination);
}
return $this->storage->putStream($destination, $resource);
}
public function setPathPrefix(string $path_prefix)
{
$this->path_prefix = $this->addSeparators($path_prefix);
}
public function getSeparator()
{
return $this->separator;
}
public function getPathPrefix(): string
{
return $this->path_prefix;
}
public function getDirectoryCollection(string $path, bool $recursive = false): DirectoryCollection
{
$collection = new DirectoryCollection($path);
foreach ($this->storage->listContents($this->applyPathPrefix($path), $recursive) as $entry) {
// By default only 'path' and 'type' is present
$name = $this->getBaseName($entry['path']);
$userpath = $this->stripPathPrefix($entry['path']);
$dirname = isset($entry['dirname']) ? $entry['dirname'] : $path;
$size = isset($entry['size']) ? $entry['size'] : 0;
$timestamp = isset($entry['timestamp']) ? $entry['timestamp'] : 0;
$collection->addFile($entry['type'], $userpath, $name, $size, $timestamp);
}
if (! $recursive && $this->addSeparators($path) !== $this->separator) {
$collection->addFile('back', $this->getParent($path), '..', 0, 0);
}
return $collection;
}
protected function upcountCallback($matches)
{
$index = isset($matches[1]) ? intval($matches[1]) + 1 : 1;
$ext = isset($matches[2]) ? $matches[2] : '';
return ' ('.$index.')'.$ext;
}
protected function upcountName($name)
{
return preg_replace_callback(
'/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/',
[$this, 'upcountCallback'],
$name,
1
);
}
private function applyPathPrefix(string $path): string
{
return $this->joinPaths($this->getPathPrefix(), $path);
}
private function stripPathPrefix(string $path): string
{
$path = $this->separator.ltrim($path, $this->separator);
if (substr($path, 0, strlen($this->getPathPrefix())) == $this->getPathPrefix()) {
$path = $this->separator.substr($path, strlen($this->getPathPrefix()));
}
return $path;
}
private function addSeparators(string $dir): string
{
if (! $dir || $dir == $this->separator || ! trim($dir, $this->separator)) {
return $this->separator;
}
return $this->separator.trim($dir, $this->separator).$this->separator;
}
private function joinPaths(string $path1, string $path2): string
{
if (! $path2 || ! trim($path2, $this->separator)) {
return $this->addSeparators($path1);
}
return $this->addSeparators($path1).ltrim($path2, $this->separator);
}
private function getParent(string $dir): string
{
if (! $dir || $dir == $this->separator || ! trim($dir, $this->separator)) {
return $this->separator;
}
$tmp = explode($this->separator, trim($dir, $this->separator));
array_pop($tmp);
return $this->separator.trim(implode($this->separator, $tmp), $this->separator);
}
private function getBaseName(string $path): string
{
if (! $path || $path == $this->separator || ! trim($path, $this->separator)) {
return $this->separator;
}
$tmp = explode($this->separator, trim($path, $this->separator));
return array_pop($tmp);
}
}

View File

@@ -0,0 +1,136 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Tmpfs\Adapters;
use Filegator\Services\Service;
use Filegator\Services\Tmpfs\TmpfsInterface;
class Tmpfs implements Service, TmpfsInterface
{
protected $path;
public function init(array $config = [])
{
$this->path = $config['path'];
if (! is_dir($this->path)) {
mkdir($this->path);
}
if (mt_rand(0, 99) < $config['gc_probability_perc']) {
$this->clean($config['gc_older_than']);
}
}
public function write(string $filename, $data)
{
$filename = $this->sanitizeFilename($filename);
file_put_contents($this->getPath().$filename, $data);
}
public function getFileLocation(string $filename): string
{
$filename = $this->sanitizeFilename($filename);
$realPath = $this->getPath().$filename;
if (! is_file($realPath)) {
touch($realPath);
}
return $realPath;
}
public function read(string $filename): string
{
$filename = $this->sanitizeFilename($filename);
return file_get_contents($this->getPath().$filename);
}
public function readStream(string $filename): array
{
$filename = $this->sanitizeFilename($filename);
$stream = fopen($this->getPath().$filename, 'r');
return [
'filename' => $filename,
'stream' => $stream,
];
}
public function exists(string $filename): bool
{
$filename = $this->sanitizeFilename($filename);
return file_exists($this->getPath().$filename);
}
public function findAll($pattern): array
{
$files = [];
foreach (glob($this->getPath().$pattern) as $filename) {
if (is_file($filename)) {
$files[] = [
'name' => basename($filename),
'size' => filesize($filename),
'time' => filemtime($filename),
];
}
}
return $files;
}
public function remove(string $filename)
{
$filename = $this->sanitizeFilename($filename);
unlink($this->getPath().$filename);
}
public function clean(int $older_than)
{
$files = $this->findAll('*');
foreach ($files as $file) {
if (time() - $file['time'] >= $older_than) {
$this->remove($file['name']);
}
}
}
private function getPath(): string
{
return $this->path;
}
private function sanitizeFilename($filename)
{
$filename = preg_replace(
'~
[<>:"/\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[;\\\{}^\~`] # other non-safe
~x',
'-',
$filename
);
// maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
$ext = pathinfo($filename, PATHINFO_EXTENSION);
return mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)).($ext ? '.'.$ext : '');
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\Tmpfs;
interface TmpfsInterface
{
public function exists(string $filename): bool;
public function findAll($pattern): array;
public function write(string $filename, $data);
public function read(string $filename): string;
public function readStream(string $filename): array;
public function remove(string $filename);
public function getFileLocation(string $filename): string;
public function clean(int $older_than);
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\View\Adapters;
use Filegator\Config\Config;
use Filegator\Services\Service;
use Filegator\Services\View\ViewInterface;
class Vuejs implements Service, ViewInterface
{
private $config;
public function __construct(Config $config)
{
$this->config = $config;
}
public function init(array $config = [])
{
}
public function getIndexPage()
{
$title = $this->config->get('frontend_config.app_name');
$public_path = $this->config->get('public_path');
$public_dir = $this->config->get('public_dir');
return '<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>'.$title.'</title>
<link rel=stylesheet href=https://use.fontawesome.com/releases/v5.2.0/css/all.css>
<link rel=stylesheet href=//cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css>
<link href="'.$public_path.'css/app.css?'.@filemtime($public_dir.'/css/app.css').'" rel=stylesheet>
</head>
<body>
<noscript><strong>Please enable JavaScript to continue.</strong></noscript>
<div id=app></div>
<script src="'.$public_path.'js/app.js?'.@filemtime($public_dir.'/js/app.js').'"></script>
<script src="'.$public_path.'js/chunk-vendors.js?'.@filemtime($public_dir.'/js/chunk-vendors.js').'"></script>
</body>
</html>
';
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the FileGator package.
*
* (c) Milos Stojanovic <alcalbg@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
*/
namespace Filegator\Services\View;
interface ViewInterface
{
public function getIndexPage();
}