diff --git a/app/Client/Factory.php b/app/Client/Factory.php index d391397..8908c3b 100644 --- a/app/Client/Factory.php +++ b/app/Client/Factory.php @@ -2,6 +2,7 @@ namespace App\Client; +use App\Client\Fileserver\Fileserver; use App\Client\Http\Controllers\AttachDataToLogController; use App\Client\Http\Controllers\ClearLogsController; use App\Client\Http\Controllers\CreateTunnelController; @@ -33,6 +34,9 @@ class Factory /** @var App */ protected $app; + /** @var Fileserver */ + protected $fileserver; + /** @var RouteGenerator */ protected $router; @@ -116,6 +120,15 @@ class Factory return $this; } + public function shareFolder(string $folder, string $name, $subdomain = null) + { + $host = $this->createFileServer($folder, $name); + + $this->share($host, $subdomain); + + return $this; + } + protected function addRoutes() { $this->router->get('/', DashboardController::class); @@ -134,18 +147,18 @@ class Factory } } - protected function detectNextFreeDashboardPort($port = 4040): int + protected function detectNextAvailablePort($startPort = 4040): int { - while (is_resource(@fsockopen('127.0.0.1', $port))) { - $port++; + while (is_resource(@fsockopen('127.0.0.1', $startPort))) { + $startPort++; } - return $port; + return $startPort; } public function createHttpServer() { - $dashboardPort = $this->detectNextFreeDashboardPort(); + $dashboardPort = $this->detectNextAvailablePort(); config()->set('expose.dashboard_port', $dashboardPort); @@ -156,11 +169,25 @@ class Factory return $this; } + public function createFileServer(string $folder, string $name) + { + $port = $this->detectNextAvailablePort(8090); + + $this->fileserver = new Fileserver($folder, $name, $port, '0.0.0.0', $this->loop); + + return "127.0.0.1:{$port}"; + } + public function getApp(): App { return $this->app; } + public function getFileserver(): Fileserver + { + return $this->fileserver; + } + public function run() { $this->loop->run(); diff --git a/app/Client/Fileserver/ConnectionHandler.php b/app/Client/Fileserver/ConnectionHandler.php new file mode 100644 index 0000000..e249e00 --- /dev/null +++ b/app/Client/Fileserver/ConnectionHandler.php @@ -0,0 +1,137 @@ +rootFolder = $rootFolder; + $this->name = $name; + $this->loop = $loop; + } + + public function handle(ServerRequestInterface $request) + { + $request = $this->createLaravelRequest($request); + $targetPath = realpath($this->rootFolder . DIRECTORY_SEPARATOR . $request->path()); + + if (! $this->isValidTarget($targetPath)) { + return new Response(404); + } + + if (is_dir($targetPath)) { + // Directory listing + $directoryContent = Finder::create() + ->depth(0) + ->sort(function ($a, $b) { + return strcmp(strtolower($a->getRealpath()), strtolower($b->getRealpath())); + }) + ->in($targetPath); + + if ($this->name !== '') { + $directoryContent->name($this->name); + } + + $parentPath = explode('/', $request->path()); + array_pop($parentPath); + $parentPath = implode('/', $parentPath); + + return new Response( + 200, + ['Content-Type' => 'text/html'], + $this->getView(null, 'client.fileserver', [ + 'currentPath' => $request->path(), + 'parentPath' => $parentPath, + 'directory' => $targetPath, + 'directoryContent' => $directoryContent + ]) + ); + } + + if (is_file($targetPath)) { + return new Response( + 200, + ['Content-Type' => mime_content_type($targetPath)], + new ReadableResourceStream(fopen($targetPath, 'r'), $this->loop) + ); + } + } + + protected function isValidTarget(string $targetPath): bool + { + if (! file_exists($targetPath)) { + return false; + } + + if ($this->name !== '') { + $filter = new class(basename($targetPath), [$this->name]) extends FilenameFilterIterator { + protected $filename; + + public function __construct(string $filename, array $matchPatterns) + { + $this->filename = $filename; + + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + } + + public function accept() + { + return $this->isAccepted($this->filename); + } + }; + return $filter->accept(); + } + + return true; + } + + protected function createLaravelRequest(ServerRequestInterface $request): Request + { + try { + parse_str($request->getBody(), $bodyParameters); + } catch (\Throwable $e) { + $bodyParameters = []; + } + + $serverRequest = (new ServerRequest( + $request->getMethod(), + $request->getUri(), + $request->getHeaders(), + $request->getBody(), + $request->getProtocolVersion(), + )) + ->withQueryParams(QueryParameters::create($request)->all()) + ->withParsedBody($bodyParameters); + + return Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest)); + } +} diff --git a/app/Client/Fileserver/Fileserver.php b/app/Client/Fileserver/Fileserver.php new file mode 100644 index 0000000..af5fbc9 --- /dev/null +++ b/app/Client/Fileserver/Fileserver.php @@ -0,0 +1,31 @@ +handle($request); + }); + + $this->socket = new SocketServer("{$address}:{$port}", $loop); + + $server->listen($this->socket); + } + + public function getSocket(): SocketServer + { + return $this->socket; + } +} diff --git a/app/Commands/ShareFilesCommand.php b/app/Commands/ShareFilesCommand.php new file mode 100644 index 0000000..7836454 --- /dev/null +++ b/app/Commands/ShareFilesCommand.php @@ -0,0 +1,48 @@ +bind(CliRequestLogger::class, function () { + return new CliRequestLogger(new ConsoleOutput()); + }); + + return $this; + } + + public function handle() + { + if ( !is_dir($this->argument('folder'))) { + throw new \InvalidArgumentException('The folder '.$this->argument('folder').' does not exist.'); + } + + $this->configureConnectionLogger(); + + (new Factory()) + ->setLoop(app(LoopInterface::class)) + ->setHost(config('expose.host', 'localhost')) + ->setPort(config('expose.port', 8080)) + ->setAuth($this->option('auth')) + ->createClient() + ->shareFolder( + $this->argument('folder'), + $this->option('name') ?? '', + explode(',', $this->option('subdomain')) + ) + ->createHttpServer() + ->run(); + } +} diff --git a/app/Http/Controllers/Concerns/LoadsViews.php b/app/Http/Controllers/Concerns/LoadsViews.php index fd60ca8..a8e0a76 100644 --- a/app/Http/Controllers/Concerns/LoadsViews.php +++ b/app/Http/Controllers/Concerns/LoadsViews.php @@ -9,7 +9,7 @@ use Twig\Loader\ArrayLoader; trait LoadsViews { - protected function getView(ConnectionInterface $connection, string $view, array $data = []) + protected function getView(?ConnectionInterface $connection, string $view, array $data = []) { $templatePath = implode(DIRECTORY_SEPARATOR, explode('.', $view)); @@ -23,7 +23,10 @@ trait LoadsViews $data = array_merge($data, [ 'request' => $connection->laravelRequest ?? null, ]); - +try { return stream_for($twig->render('template', $data)); +} catch (\Throwable $e) { + var_dump($e->getMessage()); +} } } diff --git a/resources/views/client/fileserver.twig b/resources/views/client/fileserver.twig new file mode 100644 index 0000000..1592317 --- /dev/null +++ b/resources/views/client/fileserver.twig @@ -0,0 +1,93 @@ + +
++ {{ directory }} +
+| + Name + | ++ Date Modified + | ++ Size + | +
|---|---|---|
| + Back + | +||
| + {% if currentPath != '/' %} + {{ item.filename }} + {% else %} + {{ item.filename }} + {% endif %} + | ++ {{ item.getMTime() | date("m/d/Y H:i:s") }} + | ++ {% if item.isDir() %} + - + {% else %} + {{ _self.bytesToSize(item.getSize()) }} + {% endif %} + | +