diff --git a/app/Client/Client.php b/app/Client/Client.php index 3a17771..3339db7 100644 --- a/app/Client/Client.php +++ b/app/Client/Client.php @@ -2,40 +2,51 @@ namespace App\Client; +use App\Client\Connections\ControlConnection; +use Ratchet\Client\WebSocket; use React\EventLoop\LoopInterface; -use React\Socket\ConnectionInterface; -use React\Socket\Connector; +use function Ratchet\Client\connect; class Client { /** @var LoopInterface */ protected $loop; - protected $host; - protected $port; + + /** @var Configuration */ + protected $configuration; + public static $subdomains = []; - public function __construct(LoopInterface $loop, $host, $port) + public function __construct(LoopInterface $loop, Configuration $configuration) { $this->loop = $loop; - $this->host = $host; - $this->port = $port; + $this->configuration = $configuration; } - public function share($sharedUrl, array $subdomains = []) + public function share(string $sharedUrl, array $subdomains = []) { foreach ($subdomains as $subdomain) { - $connector = new Connector($this->loop); - - $connector->connect("{$this->host}:{$this->port}") - ->then(function (ConnectionInterface $clientConnection) use ($sharedUrl, $subdomain) { - $connection = Connection::create($clientConnection, new ProxyManager($this->host, $this->port, $this->loop)); - $connection->authenticate($sharedUrl, $subdomain); - - $clientConnection->on('authenticated', function ($data) { - static::$subdomains[] = "$data->subdomain.{$this->host}:{$this->port}"; - dump("Connected to http://$data->subdomain.{$this->host}:{$this->port}"); - }); - }); + $this->connectToServer($sharedUrl, $subdomain); } } + + protected function connectToServer(string $sharedUrl, $subdomain) + { + connect("ws://{$this->configuration->host()}:{$this->configuration->port()}/__expose_control__", [], [ + 'X-Expose-Control' => 'enabled', + ], $this->loop) + ->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain) { + $connection = ControlConnection::create($clientConnection); + + $connection->authenticate($sharedUrl, $subdomain); + + $connection->on('authenticated', function ($data) { + dump("Connected to http://$data->subdomain.{$this->configuration->host()}:{$this->configuration->port()}"); + static::$subdomains[] = "$data->subdomain.{$this->configuration->host()}:{$this->configuration->port()}"; + }); + + }, function ($e) { + echo "Could not connect: {$e->getMessage()}\n"; + }); + } } diff --git a/app/Client/Configuration.php b/app/Client/Configuration.php new file mode 100644 index 0000000..423fdc0 --- /dev/null +++ b/app/Client/Configuration.php @@ -0,0 +1,39 @@ +host = $host; + + $this->port = $port; + + $this->auth = $auth; + } + + public function host(): string + { + return $this->host; + } + + public function auth(): ?string + { + return $this->auth; + } + + public function port(): int + { + return $this->port; + } +} diff --git a/app/Client/Connection.php b/app/Client/Connection.php deleted file mode 100644 index 6b1fc58..0000000 --- a/app/Client/Connection.php +++ /dev/null @@ -1,81 +0,0 @@ -socket = $socketConnection; - $this->proxyManager = $proxyManager; - - $this->socket->on('data', function ($data) { - $jsonStrings = explode("||", $data); - - $decodedEntries = []; - - foreach ($jsonStrings as $jsonString) { - try { - $decodedJsonObject = json_decode($jsonString); - if (is_object($decodedJsonObject)) { - $decodedEntries[] = $decodedJsonObject; - } - } catch (Throwable $e) { - // Ignore payload - } - } - - foreach ($decodedEntries as $decodedEntry) { - if (method_exists($this, $decodedEntry->event ?? '')) { - $this->socket->emit($decodedEntry->event, [$decodedEntry]); - - call_user_func([$this, $decodedEntry->event], $decodedEntry); - } - } - }); - } - - public function authenticated($data) - { - $this->socket->_id = $data->client_id; - - $this->createProxy($data); - } - - public function createProxy($data) - { - $this->proxyManager->createProxy($this->socket, $data); - } - - public function authenticate(string $sharedHost, string $subdomain) - { - $this->socket->write(json_encode([ - 'event' => 'authenticate', - 'data' => [ - 'host' => $sharedHost, - 'subdomain' => empty($subdomain) ? null : $subdomain, - ], - ])); - } - - public function ping() - { - $this->socket->write(json_encode([ - 'event' => 'pong', - ])); - } -} diff --git a/app/Client/Connections/ControlConnection.php b/app/Client/Connections/ControlConnection.php new file mode 100644 index 0000000..457064b --- /dev/null +++ b/app/Client/Connections/ControlConnection.php @@ -0,0 +1,72 @@ +socket = $socketConnection; + $this->proxyManager = $proxyManager; + + $this->socket->on('message', function (Message $message) { + $decodedEntry = json_decode($message); + + if (method_exists($this, $decodedEntry->event ?? '')) { + $this->emit($decodedEntry->event, [$decodedEntry]); + + call_user_func([$this, $decodedEntry->event], $decodedEntry); + } + }); + } + + public function authenticated($data) + { + $this->clientId = $data->client_id; + } + + public function createProxy($data) + { + $this->proxyManager->createProxy($this->clientId, $data); + } + + public function authenticate(string $sharedHost, string $subdomain) + { + $this->socket->send(json_encode([ + 'event' => 'authenticate', + 'data' => [ + 'host' => $sharedHost, + 'subdomain' => empty($subdomain) ? null : $subdomain, + ], + ])); + } + + public function ping() + { + $this->socket->send(json_encode([ + 'event' => 'pong', + ])); + } +} diff --git a/app/Client/Factory.php b/app/Client/Factory.php index 4008432..438da80 100644 --- a/app/Client/Factory.php +++ b/app/Client/Factory.php @@ -2,6 +2,7 @@ namespace App\Client; +use App\Client\Http\HttpClient; use App\HttpServer\App; use App\HttpServer\Controllers\AttachDataToLogController; use App\HttpServer\Controllers\ClearLogsController; @@ -23,6 +24,9 @@ class Factory /** @var int */ protected $port = 8080; + /** @var string */ + protected $auth = ''; + /** @var \React\EventLoop\LoopInterface */ protected $loop; @@ -48,6 +52,13 @@ class Factory return $this; } + public function setAuth(string $auth) + { + $this->auth = $auth; + + return $this; + } + public function setLoop(LoopInterface $loop) { $this->loop = $loop; @@ -55,22 +66,39 @@ class Factory return $this; } - public function createClient($sharedUrl, $subdomain = null) + protected function bindConfiguration() { - $client = new Client($this->loop, $this->host, $this->port); - $client->share($sharedUrl, $subdomain); + app()->singleton(Configuration::class, function ($app) { + return new Configuration($this->host, $this->port, $this->auth); + }); + } + + protected function bindProxyManager() + { + app()->singleton(ProxyManager::class, function ($app) { + return new ProxyManager($app->make(Configuration::class), $this->loop, $app->make(HttpClient::class)); + }); + } + + public function createClient($sharedUrl, $subdomain = null, $auth = null) + { + $this->bindConfiguration(); + + $this->bindProxyManager(); + + app(Client::class)->share($sharedUrl, $subdomain); return $this; } protected function addRoutes() { - $dashboardRoute = new Route('/', ['_controller' => new DashboardController()], [], [], null, [], ['GET']); - $logRoute = new Route('/logs', ['_controller' => new LogController()], [], [], null, [], ['GET']); - $storeLogRoute = new Route('/logs', ['_controller' => new StoreLogController()], [], [], null, [], ['POST']); - $replayLogRoute = new Route('/replay/{log}', ['_controller' => new ReplayLogController()], [], [], null, [], ['GET']); - $attachLogDataRoute = new Route('/logs/{request_id}/data', ['_controller' => new AttachDataToLogController()], [], [], null, [], ['POST']); - $clearLogsRoute = new Route('/logs/clear', ['_controller' => new ClearLogsController()], [], [], null, [], ['GET']); + $dashboardRoute = new Route('/', ['_controller' => app(DashboardController::class)], [], [], null, [], ['GET']); + $logRoute = new Route('/logs', ['_controller' => app(LogController::class)], [], [], null, [], ['GET']); + $storeLogRoute = new Route('/logs', ['_controller' => app(StoreLogController::class)], [], [], null, [], ['POST']); + $replayLogRoute = new Route('/replay/{log}', ['_controller' => app(ReplayLogController::class)], [], [], null, [], ['GET']); + $attachLogDataRoute = new Route('/logs/{request_id}/data', ['_controller' => app(AttachDataToLogController::class)], [], [], null, [], ['POST']); + $clearLogsRoute = new Route('/logs/clear', ['_controller' => app(ClearLogsController::class)], [], [], null, [], ['GET']); $this->app->route('/socket', new WsServer(new Socket()), ['*']); diff --git a/app/Client/Http/HttpClient.php b/app/Client/Http/HttpClient.php new file mode 100644 index 0000000..48b144f --- /dev/null +++ b/app/Client/Http/HttpClient.php @@ -0,0 +1,147 @@ +loop = $loop; + $this->logger = $logger; + } + + public function performRequest(string $requestData, WebSocket $proxyConnection = null, string $requestId = null) + { + $this->request = $this->parseRequest($requestData); + + $this->logger->logRequest($requestData, $this->request); + + $request = $this->passRequestThroughModifiers(parse_request($requestData), $proxyConnection); + + dump($this->request->getMethod() . ' ' . $this->request->getUri()->getPath()); + + /** + * Modifiers can already send a response to the proxy connection, + * which would result in the request being null. + */ + if (is_null($request)) { + return; + } + + $this->sendRequestToApplication($request, $proxyConnection); + } + + protected function passRequestThroughModifiers(RequestInterface $request, ?WebSocket $proxyConnection = null): ?RequestInterface + { + foreach ($this->modifiers as $modifier) { + $request = app($modifier)->handle($request, $proxyConnection); + + if (is_null($request)) { + break; + } + } + + return $request; + } + + protected function createConnector(): Connector + { + return new Connector($this->loop, array( + 'dns' => '127.0.0.1', + 'tls' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + ) + )); + } + + protected function sendRequestToApplication(RequestInterface $request, $proxyConnection = null) + { + (new Browser($this->loop, $this->createConnector())) + ->withOptions([ + 'followRedirects' => false, + 'obeySuccessCode' => false, + 'streaming' => true, + ]) + ->send($request) + ->then(function (ResponseInterface $response) use ($proxyConnection) { + if (! isset($response->buffer)) { + $response->buffer = str($response); + } + + $this->sendChunkToServer($response->buffer, $proxyConnection); + + /* @var $body \React\Stream\ReadableStreamInterface */ + $body = $response->getBody(); + + $this->logResponse($response->buffer); + + $body->on('data', function ($chunk) use ($proxyConnection, $response) { + $response->buffer .= $chunk; + + $this->sendChunkToServer($chunk, $proxyConnection); + + if ($chunk === "") { + $this->logResponse($response->buffer); + + optional($proxyConnection)->close(); + } + }); + + $body->on('close', function () use ($proxyConnection, $response) { + $this->logResponse($response->buffer); + + optional($proxyConnection)->close(); + }); + }); + } + + protected function sendChunkToServer(string $chunk, ?WebSocket $proxyConnection = null) + { + if (is_null($proxyConnection)) { + return; + } + + $binaryMsg = new Frame($chunk, true, Frame::OP_BINARY); + $proxyConnection->send($binaryMsg); + } + + protected function logResponse(string $rawResponse) + { + $this->logger->logResponse($this->request, $rawResponse); + } + + protected function parseRequest($data): Request + { + return Request::fromString($data); + } +} diff --git a/app/Client/Http/Modifiers/CheckBasicAuthentication.php b/app/Client/Http/Modifiers/CheckBasicAuthentication.php new file mode 100644 index 0000000..f76f3ab --- /dev/null +++ b/app/Client/Http/Modifiers/CheckBasicAuthentication.php @@ -0,0 +1,98 @@ +configuration = $configuration; + } + + public function handle(RequestInterface $request, WebSocket $proxyConnection): ?RequestInterface + { + if (! $this->requiresAuthentication() || is_null($proxyConnection)) { + return $request; + } + + $username = $this->getAuthorizationUsername($request); + + if (is_null($username)) { + $proxyConnection->send( + str(new \GuzzleHttp\Psr7\Response(401, [ + 'WWW-Authenticate' => 'Basic realm=Expose' + ], 'Unauthorized')) + ); + $proxyConnection->close(); + return null; + } + + return $request; + } + + protected function getAuthorizationUsername(RequestInterface $request): ?string + { + $authorization = $this->parseAuthorizationHeader(Arr::get($request->getHeaders(), 'authorization.0', '')); + $credentials = $this->getCredentials(); + + if (empty($authorization)) { + return null; + } + + if (!array_key_exists($authorization['username'], $credentials)) { + return null; + } + + if ($credentials[$authorization['username']] !== $authorization['password']) { + return null; + } + + return $authorization['username']; + } + + protected function parseAuthorizationHeader(string $header) + { + if (strpos($header, 'Basic') !== 0) { + return null; + } + + $header = base64_decode(substr($header, 6)); + + if ($header === false) { + return null; + } + + $header = explode(':', $header, 2); + + return [ + 'username' => $header[0], + 'password' => isset($header[1]) ? $header[1] : null, + ]; + } + + protected function requiresAuthentication(): bool + { + return !empty($this->getCredentials()); + } + + protected function getCredentials() + { + try { + $credentials = explode(':', $this->configuration->auth()); + return [ + $credentials[0] => $credentials[1], + ]; + } catch (\Exception $e) { + return []; + } + } +} diff --git a/app/Client/ProxyManager.php b/app/Client/ProxyManager.php index 0e8b0af..386f6e7 100644 --- a/app/Client/ProxyManager.php +++ b/app/Client/ProxyManager.php @@ -2,69 +2,52 @@ namespace App\Client; -use App\Logger\RequestLogger; -use BFunky\HttpParser\HttpRequestParser; -use BFunky\HttpParser\HttpResponseParser; -use Illuminate\Support\Arr; -use Illuminate\Support\Str; -use React\Socket\ConnectionInterface; -use React\Socket\Connector; -use React\Stream\ThroughStream; -use React\Stream\Util; -use React\Stream\WritableResourceStream; -use GuzzleHttp\Psr7 as gPsr; -use function GuzzleHttp\Psr7\parse_request; +use App\Client\Http\HttpClient; +use Ratchet\Client\WebSocket; +use Ratchet\ConnectionInterface; +use React\EventLoop\LoopInterface; +use function Ratchet\Client\connect; class ProxyManager { - private $host; - private $port; - private $loop; + /** @var Configuration */ + protected $configuration; - public function __construct($host, $port, $loop) + /** @var LoopInterface */ + protected $loop; + + /** @var HttpClient */ + protected $httpClient; + + public function __construct(Configuration $configuration, LoopInterface $loop, HttpClient $httpClient) { - $this->host = $host; - $this->port = $port; + $this->configuration = $configuration; $this->loop = $loop; + $this->httpClient = $httpClient; } - public function createProxy(ConnectionInterface $clientConnection, $connectionData) + public function createProxy(string $clientId, $connectionData) { - $connector = new Connector($this->loop); - $connector->connect("{$this->host}:{$this->port}")->then(function (ConnectionInterface $proxyConnection) use ($clientConnection, $connector, $connectionData) { - $proxyConnection->write(json_encode([ - 'event' => 'registerProxy', - 'data' => [ - 'request_id' => $connectionData->request_id ?? null, - 'client_id' => $clientConnection->_id, - ], - ])); + connect("ws://{$this->configuration->host()}:{$this->configuration->port()}/__expose_control__", [], [ + 'X-Expose-Control' => 'enabled', + ], $this->loop) + ->then(function (WebSocket $proxyConnection) use ($clientId, $connectionData) { + $proxyConnection->on('message', function ($message) use ($proxyConnection, $connectionData) { + $this->performRequest($proxyConnection, $connectionData->request_id, (string)$message); + }); - $proxyConnection->on('data', function ($data) use (&$proxyData, $proxyConnection, $connector) { - if (!isset($proxyConnection->buffer)) { - $proxyConnection->buffer = ''; - } - - $proxyConnection->buffer .= $data; - - if ($this->hasBufferedAllData($proxyConnection)) { - $tunnel = app(TunnelConnection::class); - - $tunnel->performRequest($proxyConnection->buffer, $proxyConnection); - } + $proxyConnection->send(json_encode([ + 'event' => 'registerProxy', + 'data' => [ + 'request_id' => $connectionData->request_id ?? null, + 'client_id' => $clientId, + ], + ])); }); - }); } - protected function getContentLength($proxyConnection): ?int + protected function performRequest(WebSocket $proxyConnection, $requestId, string $requestData) { - $request = parse_request($proxyConnection->buffer); - - return Arr::first($request->getHeader('Content-Length')); - } - - protected function hasBufferedAllData($proxyConnection) - { - return is_null($this->getContentLength($proxyConnection)) || strlen(Str::after($proxyConnection->buffer, "\r\n\r\n")) >= $this->getContentLength($proxyConnection); + $this->httpClient->performRequest((string)$requestData, $proxyConnection, $requestId); } } diff --git a/app/Client/TunnelConnection.php b/app/Client/TunnelConnection.php deleted file mode 100644 index beee5c9..0000000 --- a/app/Client/TunnelConnection.php +++ /dev/null @@ -1,166 +0,0 @@ -loop = $loop; - $this->logger = $logger; - } - - protected function requiresAuthentication(): bool - { - return !empty($this->getCredentials()); - } - - public function performRequest($requestData, ConnectionInterface $proxyConnection = null) - { - $this->request = $this->parseRequest($requestData); - - $this->logger->logRequest($requestData, $this->request); - - dump($this->request->getMethod() . ' ' . $this->request->getUri()->getPath()); - - if ($this->requiresAuthentication() && !is_null($proxyConnection)) { - $username = $this->getAuthorizationUsername(); - if (is_null($username)) { - $proxyConnection->write( - str(new \GuzzleHttp\Psr7\Response(401, [ - 'WWW-Authenticate' => 'Basic realm=Expose' - ], 'Unauthorized')) - ); - $proxyConnection->end(); - return; - } - } - - (new Connector($this->loop)) - ->connect("localhost:80") - ->then(function (ConnectionInterface $connection) use ($requestData, $proxyConnection) { - $connection->on('data', function ($data) use (&$chunks, &$contentLength, $connection, $proxyConnection) { - if (!isset($connection->httpBuffer)) { - $connection->httpBuffer = ''; - } - - $connection->httpBuffer .= $data; - - $response = $this->parseResponse($connection->httpBuffer); - - if (! is_null($response) && $this->hasBufferedAllData($connection)) { - - $this->logger->logResponse($this->request, $connection->httpBuffer, $response); - - if (! is_null($proxyConnection)) { - $proxyConnection->write($connection->httpBuffer); - } - - unset($proxyConnection->buffer); - - unset($connection->httpBuffer); - } - - }); - $connection->write($requestData); - }); - } - - protected function getContentLength($connection): ?int - { - $response = $this->parseResponse($connection->httpBuffer); - - return Arr::get($response->getHeaders()->toArray(), 'Content-Length'); - } - - protected function hasBufferedAllData($connection) - { - return is_null($this->getContentLength($connection)) || strlen(Str::after($connection->httpBuffer, "\r\n\r\n")) === $this->getContentLength($connection); - } - - protected function parseResponse(string $response) - { - try { - return Response::fromString($response); - } catch (\Throwable $e) { - return null; - } - } - - protected function parseRequest($data): Request - { - return Request::fromString($data); - } - - protected function getCredentials() - { - try { - $credentials = explode(':', $GLOBALS['expose.auth']); - return [ - $credentials[0] => $credentials[1], - ]; - } catch (\Exception $e) { - return []; - } - } - - protected function getAuthorizationUsername(): ?string - { - $authorization = $this->parseAuthorizationHeader(Arr::get($this->request->getHeaders()->toArray(), 'Authorization', '')); - $credentials = $this->getCredentials(); - - if (empty($authorization)) { - return null; - } - - if (!array_key_exists($authorization['username'], $credentials)) { - return null; - } - - if ($credentials[$authorization['username']] !== $authorization['password']) { - return null; - } - - return $authorization['username']; - } - - protected function parseAuthorizationHeader(string $header) - { - if (strpos($header, 'Basic') !== 0) { - return null; - } - - $header = base64_decode(substr($header, 6)); - - if ($header === false) { - return null; - } - - $header = explode(':', $header, 2); - - return [ - 'username' => $header[0], - 'password' => isset($header[1]) ? $header[1] : null, - ]; - } -} diff --git a/app/Commands/ShareCommand.php b/app/Commands/ShareCommand.php index 4609599..d549229 100644 --- a/app/Commands/ShareCommand.php +++ b/app/Commands/ShareCommand.php @@ -15,15 +15,11 @@ class ShareCommand extends Command public function handle() { - // TODO: Hacky workaround just to see if it works haha - if ($this->option('auth')) { - $GLOBALS['expose.auth'] = $this->option('auth'); - } - (new Factory()) ->setLoop(app(LoopInterface::class)) // ->setHost('beyond.sh') // TODO: Read from (local/global) config file // ->setPort(8080) // TODO: Read from (local/global) config file + ->setAuth($this->option('auth')) ->createClient($this->argument('host'), explode(',', $this->option('subdomain'))) ->createHttpServer() ->run(); diff --git a/app/Contracts/ConnectionManager.php b/app/Contracts/ConnectionManager.php new file mode 100644 index 0000000..69c66da --- /dev/null +++ b/app/Contracts/ConnectionManager.php @@ -0,0 +1,21 @@ +findLoggedRequest($request->get('request_id', '')); + $this->requestLogger = $requestLogger; + } + + public function handle(Request $request, ConnectionInterface $httpConnection) + { + $loggedRequest = $this->requestLogger->findLoggedRequest($request->get('request_id', '')); if (! is_null($loggedRequest)) { $loggedRequest->setAdditionalData((array)$request->get('data', [])); - $requestLogger->pushLogs(); + $this->requestLogger->pushLogs(); } } } diff --git a/app/HttpServer/Controllers/ClearLogsController.php b/app/HttpServer/Controllers/ClearLogsController.php index 453404c..7f53b87 100644 --- a/app/HttpServer/Controllers/ClearLogsController.php +++ b/app/HttpServer/Controllers/ClearLogsController.php @@ -2,7 +2,7 @@ namespace App\HttpServer\Controllers; -use App\Client\TunnelConnection; +use App\Client\Http\HttpClient; use App\HttpServer\QueryParameters; use App\Logger\RequestLogger; use GuzzleHttp\Psr7\Response; @@ -12,11 +12,17 @@ use Psr\Http\Message\RequestInterface; class ClearLogsController extends Controller { + /** @var RequestLogger */ + protected $requestLogger; + + public function __construct(RequestLogger $requestLogger) + { + $this->requestLogger = $requestLogger; + } + public function onOpen(ConnectionInterface $connection, RequestInterface $request = null) { - /** @var RequestLogger $logger */ - $logger = app(RequestLogger::class); - $logger->clear(); + $this->requestLogger->clear(); $connection->send( str(new Response( diff --git a/app/HttpServer/Controllers/Controller.php b/app/HttpServer/Controllers/Controller.php index 6270148..052c08a 100644 --- a/app/HttpServer/Controllers/Controller.php +++ b/app/HttpServer/Controllers/Controller.php @@ -10,6 +10,9 @@ abstract class Controller implements HttpServerInterface { public function onClose(ConnectionInterface $connection) { + unset($connection->requestBuffer); + unset($connection->contentLength); + unset($connection->request); } public function onError(ConnectionInterface $connection, Exception $e) diff --git a/app/HttpServer/Controllers/LogController.php b/app/HttpServer/Controllers/LogController.php index 3a8bd9d..b0c76bd 100644 --- a/app/HttpServer/Controllers/LogController.php +++ b/app/HttpServer/Controllers/LogController.php @@ -10,16 +10,21 @@ use Psr\Http\Message\RequestInterface; class LogController extends Controller { + /** @var RequestLogger */ + protected $requestLogger; + + public function __construct(RequestLogger $requestLogger) + { + $this->requestLogger = $requestLogger; + } + public function onOpen(ConnectionInterface $connection, RequestInterface $request = null) { - /** @var RequestLogger $logger */ - $logger = app(RequestLogger::class); - $connection->send( str(new Response( 200, ['Content-Type' => 'application/json'], - json_encode($logger->getData(), JSON_INVALID_UTF8_IGNORE) + json_encode($this->requestLogger->getData(), JSON_INVALID_UTF8_IGNORE) )) ); diff --git a/app/HttpServer/Controllers/PostController.php b/app/HttpServer/Controllers/PostController.php index 7bce520..75a1142 100644 --- a/app/HttpServer/Controllers/PostController.php +++ b/app/HttpServer/Controllers/PostController.php @@ -9,11 +9,15 @@ use Illuminate\Support\Collection; use Psr\Http\Message\RequestInterface; use Ratchet\ConnectionInterface; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use function GuzzleHttp\Psr7\parse_request; abstract class PostController extends Controller { + protected $keepConnectionOpen = false; + public function onOpen(ConnectionInterface $connection, RequestInterface $request = null) { + dump(memory_get_usage(true)); $connection->contentLength = $this->findContentLength($request->getHeaders()); $connection->requestBuffer = (string) $request->getBody(); @@ -25,7 +29,14 @@ abstract class PostController extends Controller public function onMessage(ConnectionInterface $from, $msg) { - $from->requestBuffer .= $msg; + if (! isset($from->requestBuffer)) { + $request = parse_request($msg); + $from->contentLength = $this->findContentLength($request->getHeaders()); + $from->request = $request; + $from->requestBuffer = (string) $request->getBody(); + } else { + $from->requestBuffer .= $msg; + } $this->checkContentLength($from); } @@ -42,9 +53,11 @@ abstract class PostController extends Controller if (strlen($connection->requestBuffer) === $connection->contentLength) { $laravelRequest = $this->createLaravelRequest($connection); - $this->handle($laravelRequest); + $this->handle($laravelRequest, $connection); - $connection->close(); + if (! $this->keepConnectionOpen) { + $connection->close(); + } unset($connection->requestBuffer); unset($connection->contentLength); @@ -52,7 +65,7 @@ abstract class PostController extends Controller } } - abstract public function handle(Request $request); + abstract public function handle(Request $request, ConnectionInterface $httpConnection); protected function createLaravelRequest(ConnectionInterface $connection): Request { diff --git a/app/HttpServer/Controllers/ReplayLogController.php b/app/HttpServer/Controllers/ReplayLogController.php index 851d535..d27d2d9 100644 --- a/app/HttpServer/Controllers/ReplayLogController.php +++ b/app/HttpServer/Controllers/ReplayLogController.php @@ -2,7 +2,7 @@ namespace App\HttpServer\Controllers; -use App\Client\TunnelConnection; +use App\Client\Http\HttpClient; use App\HttpServer\QueryParameters; use App\Logger\RequestLogger; use GuzzleHttp\Psr7\Response; @@ -12,15 +12,25 @@ use Psr\Http\Message\RequestInterface; class ReplayLogController extends Controller { + /** @var RequestLogger */ + protected $requestLogger; + + /** @var HttpClient */ + protected $httpClient; + + public function __construct(RequestLogger $requestLogger, HttpClient $httpClient) + { + $this->requestLogger = $requestLogger; + $this->httpClient = $httpClient; + } + public function onOpen(ConnectionInterface $connection, RequestInterface $request = null) { /** @var RequestLogger $logger */ - $logger = app(RequestLogger::class); - $requestData = $logger->findLoggedRequest(QueryParameters::create($request)->get('log'))->getRequestData(); + $requestData = $this->requestLogger->findLoggedRequest(QueryParameters::create($request)->get('log'))->getRequestData(); - /** @var TunnelConnection $tunnel */ - $tunnel = app(TunnelConnection::class); - $tunnel->performRequest($requestData); + /** @var HttpClient $tunnel */ + $this->httpClient->performRequest($requestData); $connection->send( str(new Response( diff --git a/app/HttpServer/HttpServer.php b/app/HttpServer/HttpServer.php index 982cbfb..978898a 100644 --- a/app/HttpServer/HttpServer.php +++ b/app/HttpServer/HttpServer.php @@ -10,6 +10,6 @@ class HttpServer extends \Ratchet\Http\HttpServer { parent::__construct($component); - $this->_reqParser->maxSize = 15242880; + $this->_reqParser->maxSize = 15242880000; } } diff --git a/app/Logger/LoggedRequest.php b/app/Logger/LoggedRequest.php index 754dc9c..82687f2 100644 --- a/app/Logger/LoggedRequest.php +++ b/app/Logger/LoggedRequest.php @@ -66,18 +66,23 @@ class LoggedRequest implements \JsonSerializable 'body' => $this->isBinary($this->rawRequest) ? 'BINARY' : $this->parsedRequest->getContent(), 'query' => $this->parsedRequest->getQuery()->toArray(), 'post' => $this->getPost(), - 'curl' => (new CurlFormatter())->format(parse_request($this->rawRequest)), + 'curl' => '', //(new CurlFormatter())->format(parse_request($this->rawRequest)), 'additional_data' => $this->additionalData, ], ]; if ($this->parsedResponse) { + try { + $body = $this->parsedResponse->getBody(); + } catch (\Exception $e) { + $body = ''; + } $data['response'] = [ 'raw' => $this->shouldReturnBody() ? $this->rawResponse : 'BINARY', 'status' => $this->parsedResponse->getStatusCode(), 'headers' => $this->parsedResponse->getHeaders()->toArray(), 'reason' => $this->parsedResponse->getReasonPhrase(), - 'body' => $this->shouldReturnBody() ? $this->parsedResponse->getBody() : 'BINARY', + 'body' => $this->shouldReturnBody() ? $body : 'BINARY', ]; } @@ -112,7 +117,9 @@ class LoggedRequest implements \JsonSerializable $this->rawResponse = $rawResponse; - $this->stopTime = now(); + if (is_null($this->stopTime)) { + $this->stopTime = now(); + } } public function id() diff --git a/app/Logger/RequestLogger.php b/app/Logger/RequestLogger.php index ec8f17d..9f9cdc2 100644 --- a/app/Logger/RequestLogger.php +++ b/app/Logger/RequestLogger.php @@ -34,13 +34,13 @@ class RequestLogger $this->pushLogs(); } - public function logResponse(Request $request, string $rawResponse, Response $response) + public function logResponse(Request $request, string $rawResponse) { $loggedRequest = collect($this->requests)->first(function (LoggedRequest $loggedRequest) use ($request) { return $loggedRequest->getRequest() === $request; }); if ($loggedRequest) { - $loggedRequest->setResponse($rawResponse, $response); + $loggedRequest->setResponse($rawResponse, Response::fromString($rawResponse)); $this->pushLogs(); } diff --git a/app/Server/Configuration.php b/app/Server/Configuration.php new file mode 100644 index 0000000..e033a6f --- /dev/null +++ b/app/Server/Configuration.php @@ -0,0 +1,29 @@ +hostname = $hostname; + + $this->port = $port; + } + + public function hostname(): string + { + return $this->hostname; + } + + public function port(): int + { + return $this->port; + } +} diff --git a/app/Server/Connections/Connection.php b/app/Server/Connections/Connection.php deleted file mode 100644 index 55b210d..0000000 --- a/app/Server/Connections/Connection.php +++ /dev/null @@ -1,46 +0,0 @@ -socket = $socket; - $this->host = $host; - $this->subdomain = $subdomain; - $this->client_id = $clientId; - } - - public function registerProxy($requestId) - { - $this->socket->send(json_encode([ - 'event' => 'createProxy', - 'request_id' => $requestId, - 'client_id' => $this->client_id, - ]) . "||"); - } - - public function pipeRequestThroughProxy(HttpRequestConnection $httpConnection, string $requestId, Request $request) - { - $this->registerProxy($requestId); - - $this->socket->getConnection()->once('proxy_ready_' . $requestId, function (IoConnection $proxy) use ($request, $requestId, $httpConnection) { - Util::pipe($proxy->getConnection(), $httpConnection->getConnection()); - - $proxy->send(\GuzzleHttp\Psr7\str($request)); - }); - } -} diff --git a/app/Server/Connections/ConnectionManager.php b/app/Server/Connections/ConnectionManager.php index 4a47fff..7443d95 100644 --- a/app/Server/Connections/ConnectionManager.php +++ b/app/Server/Connections/ConnectionManager.php @@ -2,59 +2,76 @@ namespace App\Server\Connections; -use Illuminate\Support\Str; +use App\Contracts\ConnectionManager as ConnectionManagerContract; +use App\Contracts\SubdomainGenerator; use Ratchet\ConnectionInterface; -class ConnectionManager +class ConnectionManager implements ConnectionManagerContract { /** @var array */ protected $connections = []; - protected $hostname; - protected $port; - public function __construct($hostname, $port) + /** @var array */ + protected $httpConnections = []; + + /** @var SubdomainGenerator */ + protected $subdomainGenerator; + + public function __construct(SubdomainGenerator $subdomainGenerator) { - $this->hostname = $hostname; - $this->port = $port; + $this->subdomainGenerator = $subdomainGenerator; } - public function storeConnection(string $host, ?string $subdomain, IoConnection $connection) + public function storeConnection(string $host, ?string $subdomain, ConnectionInterface $connection): ControlConnection { $clientId = (string)uniqid(); - $storedConnection = new Connection($connection, $host, $subdomain ?? $this->generateSubdomain(), $clientId); + $connection->client_id = $clientId; + + $storedConnection = new ControlConnection($connection, $host, $subdomain ?? $this->subdomainGenerator->generateSubdomain(), $clientId); $this->connections[] = $storedConnection; return $storedConnection; } - public function findConnectionForSubdomain($subdomain): ?Connection + public function storeHttpConnection(ConnectionInterface $httpConnection, $requestId): ConnectionInterface + { + $this->httpConnections[$requestId] = $httpConnection; + + return $httpConnection; + } + + public function getHttpConnectionForRequestId(string $requestId): ?ConnectionInterface + { + return $this->httpConnections[$requestId] ?? null; + } + + public function removeControlConnection($connection) + { + if (isset($this->httpConnections[$connection->request_id])) { + unset($this->httpConnections[$connection->request_id]); + } + + if (isset($connection->client_id)) { + $clientId = $connection->client_id; + $this->collections = collect($this->connections)->reject(function ($connection) use ($clientId) { + return $connection->client_id == $clientId; + })->toArray(); + } + } + + public function findControlConnectionForSubdomain($subdomain): ?ControlConnection { return collect($this->connections)->last(function ($connection) use ($subdomain) { return $connection->subdomain == $subdomain; }); } - public function findConnectionForClientId(string $clientId): ?Connection + public function findControlConnectionForClientId(string $clientId): ?ControlConnection { return collect($this->connections)->last(function ($connection) use ($clientId) { return $connection->client_id == $clientId; }); } - - protected function generateSubdomain(): string - { - return strtolower(Str::random(10)); - } - - public function host() - { - return $this->hostname; - } - - public function port() - { - return $this->port; - } } diff --git a/app/Server/Connections/ControlConnection.php b/app/Server/Connections/ControlConnection.php new file mode 100644 index 0000000..bc50151 --- /dev/null +++ b/app/Server/Connections/ControlConnection.php @@ -0,0 +1,43 @@ +socket = $socket; + $this->host = $host; + $this->subdomain = $subdomain; + $this->client_id = $clientId; + } + + public function registerProxy($requestId) + { + $this->socket->send(json_encode([ + 'event' => 'createProxy', + 'request_id' => $requestId, + 'client_id' => $this->client_id, + ])); + } +} diff --git a/app/Server/Connections/HttpRequestConnection.php b/app/Server/Connections/HttpRequestConnection.php deleted file mode 100644 index d166cb3..0000000 --- a/app/Server/Connections/HttpRequestConnection.php +++ /dev/null @@ -1,76 +0,0 @@ -connection = $connection; - - if (! isset($this->connection->buffer)) { - $this->connection->buffer = ''; - } - - $this->connection->buffer .= $message; - } - - public function getRequest(): Request - { - return parse_request($this->connection->buffer); - } - - protected function getContentLength(): ?int - { - return Arr::first($this->getRequest()->getHeader('Content-Length')); - } - - public function hasBufferedAllData() - { - return is_null($this->getContentLength()) || strlen(Str::after($this->connection->buffer, "\r\n\r\n")) === $this->getContentLength(); - } - - public function getConnection() - { - return $this->connection->getConnection(); - } - - public function __get($key) - { - return $this->connection->$key; - } - - public function __set($key, $value) - { - return $this->connection->$key = $value; - } - - public function __unset($key) - { - unset($this->connection->$key); - } - - public function send($data) - { - return $this->connection->send($data); - } - - public function close() - { - return $this->connection->close(); - } -} diff --git a/app/Server/Connections/IoConnection.php b/app/Server/Connections/IoConnection.php deleted file mode 100644 index 7518932..0000000 --- a/app/Server/Connections/IoConnection.php +++ /dev/null @@ -1,44 +0,0 @@ -conn = $conn; - } - - /** - * @return ReactConn - */ - public function getConnection(): ReactConn - { - return $this->conn; - } - - /** - * {@inheritdoc} - */ - public function send($data) { - $this->conn->write($data); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function close() { - $this->conn->end(); - } -} diff --git a/app/Server/Expose.php b/app/Server/Expose.php deleted file mode 100644 index a6d178b..0000000 --- a/app/Server/Expose.php +++ /dev/null @@ -1,50 +0,0 @@ -connectionManager = $connectionManager; - } - - public function onOpen(ConnectionInterface $conn) - { - // TODO: Implement onOpen() method. - } - - public function onClose(ConnectionInterface $conn) - { - dump("close connection"); - } - - public function onError(ConnectionInterface $conn, \Exception $e) - { - // TODO: Implement onError() method. - } - - public function onMessage(ConnectionInterface $connection, $message) - { - $payload = json_decode($message); - - if (json_last_error() === JSON_ERROR_NONE) { - $message = new ControlMessage($payload, $connection, $this->connectionManager); - $message->respond(); - } else { - $message = new TunnelMessage(HttpRequestConnection::wrap($connection, $message), $this->connectionManager); - $message->respond(); - } - } -} diff --git a/app/Server/Factory.php b/app/Server/Factory.php index c6c296d..9f679c8 100644 --- a/app/Server/Factory.php +++ b/app/Server/Factory.php @@ -2,10 +2,23 @@ namespace App\Server; +use App\Contracts\ConnectionManager as ConnectionManagerContract; +use App\Contracts\SubdomainGenerator; +use App\HttpServer\HttpServer; use App\Server\Connections\ConnectionManager; +use App\Server\Http\Controllers\ControlMessageController; +use App\Server\Http\Controllers\TunnelMessageController; +use App\Server\SubdomainGenerator\RandomSubdomainGenerator; +use Ratchet\Http\Router; +use Ratchet\Server\IoServer; +use Ratchet\WebSocket\WsServer; use React\Socket\Server; use React\EventLoop\LoopInterface; use React\EventLoop\Factory as LoopFactory; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; class Factory { @@ -54,15 +67,65 @@ class Factory return $this; } + protected function getRoutes(): RouteCollection + { + $routes = new RouteCollection(); + + $routes->add('control', + new Route('/__expose_control__', [ + '_controller' => new WsServer(app(ControlMessageController::class)) + ], [], [], null, [], [] + ) + ); + + $routes->add('tunnel', + new Route('/{__catchall__}', [ + '_controller' => app(TunnelMessageController::class), + ], [ + '__catchall__' => '.*' + ])); + + return $routes; + } + + protected function bindConfiguration() + { + app()->singleton(Configuration::class, function ($app) { + return new Configuration($this->hostname, $this->port); + }); + } + + protected function bindSubdomainGenerator() + { + app()->singleton(SubdomainGenerator::class, function ($app) { + return $app->make(RandomSubdomainGenerator::class); + }); + } + + protected function bindConnectionManager() + { + app()->singleton(ConnectionManagerContract::class, function ($app) { + return $app->make(ConnectionManager::class); + }); + } + public function createServer() { $socket = new Server("{$this->host}:{$this->port}", $this->loop); - $connectionManager = new ConnectionManager($this->hostname, $this->port); + $this->bindConfiguration(); - $app = new Expose($connectionManager); + $this->bindSubdomainGenerator(); - return new IoServer($app, $socket, $this->loop); + $this->bindConnectionManager(); + + $urlMatcher = new UrlMatcher($this->getRoutes(), new RequestContext); + + $router = new Router($urlMatcher); + + $http = new HttpServer($router); + + return new IoServer($http, $socket, $this->loop); } } diff --git a/app/Server/Http/Controllers/ControlMessageController.php b/app/Server/Http/Controllers/ControlMessageController.php new file mode 100644 index 0000000..eab1a59 --- /dev/null +++ b/app/Server/Http/Controllers/ControlMessageController.php @@ -0,0 +1,98 @@ +connectionManager = $connectionManager; + } + + /** + * @inheritDoc + */ + function onClose(ConnectionInterface $connection) + { + if (isset($connection->request_id)) { + $httpConnection = $this->connectionManager->getHttpConnectionForRequestId($connection->request_id); + $httpConnection->close(); + } + + $this->connectionManager->removeControlConnection($connection); + } + + /** + * @inheritDoc + */ + function onMessage(ConnectionInterface $connection, $msg) + { + if (isset($connection->request_id)) { + return $this->sendRequestToHttpConnection($connection->request_id, $msg); + } + + try { + $payload = json_decode($msg); + $eventName = $payload->event; + + if (method_exists($this, $eventName)) { + return call_user_func([$this, $eventName], $connection, $payload->data ?? new stdClass()); + } + } catch (\Throwable $exception) { + // + } + } + + protected function sendRequestToHttpConnection(string $requestId, $request) + { + $httpConnection = $this->connectionManager->getHttpConnectionForRequestId($requestId); + $httpConnection->send($request); + } + + protected function authenticate(ConnectionInterface $connection, $data) + { + $connectionInfo = $this->connectionManager->storeConnection($data->host, $data->subdomain, $connection); + + $connection->send(json_encode([ + 'event' => 'authenticated', + 'subdomain' => $connectionInfo->subdomain, + 'client_id' => $connectionInfo->client_id + ])); + } + + protected function registerProxy(ConnectionInterface $connection, $data) + { + $connection->request_id = $data->request_id; + + $connectionInfo = $this->connectionManager->findControlConnectionForClientId($data->client_id); + + $connectionInfo->emit('proxy_ready_' . $data->request_id, [ + $connection, + ]); + } + + /** + * @inheritDoc + */ + function onOpen(ConnectionInterface $conn) + { + // + } + + /** + * @inheritDoc + */ + function onError(ConnectionInterface $conn, \Exception $e) + { + // + } +} diff --git a/app/Server/Http/Controllers/TunnelMessageController.php b/app/Server/Http/Controllers/TunnelMessageController.php new file mode 100644 index 0000000..9663d55 --- /dev/null +++ b/app/Server/Http/Controllers/TunnelMessageController.php @@ -0,0 +1,91 @@ +connectionManager = $connectionManager; + $this->configuration = $configuration; + } + + public function handle(Request $request, ConnectionInterface $httpConnection) + { + $controlConnection = $this->connectionManager->findControlConnectionForSubdomain($this->detectSubdomain($request)); + + if (is_null($controlConnection)) { + $httpConnection->send(str(new Response(404, [], 'Not found'))); + $httpConnection->close(); + return; + } + + $this->sendRequestToClient($request, $controlConnection, $httpConnection); + } + + protected function detectSubdomain(Request $request): ?string + { + $domainParts = explode('.', $request->getHost()); + + return trim($domainParts[0]); + } + + protected function sendRequestToClient(Request $request, ControlConnection $controlConnection, ConnectionInterface $httpConnection) + { + (new Pipeline(app())) + ->send($this->prepareRequest($request, $controlConnection)) + ->through($this->middleware) + ->then(function ($request) use ($controlConnection, $httpConnection) { + $requestId = $request->header('X-Expose-Request-ID'); + + $this->connectionManager->storeHttpConnection($httpConnection, $requestId); + + $controlConnection->once('proxy_ready_' . $requestId, function (ConnectionInterface $proxy) use ($request) { + // Convert the Laravel request into a PSR7 request + $psr17Factory = new Psr17Factory(); + $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + $request = $psrHttpFactory->createRequest($request); + + $binaryMsg = new Frame(str($request), true, Frame::OP_BINARY); + $proxy->send($binaryMsg); + }); + + $controlConnection->registerProxy($requestId); + }); + } + + protected function prepareRequest(Request $request, ControlConnection $controlConnection): Request + { + $request->headers->set('Host', $controlConnection->host); + $request->headers->set('X-Expose-Request-ID', uniqid()); + $request->headers->set('X-Exposed-By', config('app.name') . ' '. config('app.version')); + $request->headers->set('X-Original-Host', "{$controlConnection->subdomain}.{$this->configuration->hostname()}:{$this->configuration->port()}"); + + return $request; + } +} diff --git a/app/Server/IoServer.php b/app/Server/IoServer.php deleted file mode 100644 index 8cd2b11..0000000 --- a/app/Server/IoServer.php +++ /dev/null @@ -1,32 +0,0 @@ -decor = new IoConnection($conn); - $conn->decor->resourceId = (int)$conn->stream; - - $uri = $conn->getRemoteAddress(); - $conn->decor->remoteAddress = trim( - parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST), - '[]' - ); - - $this->app->onOpen($conn->decor); - - $conn->on('data', function ($data) use ($conn) { - $this->handleData($data, $conn); - }); - $conn->on('close', function () use ($conn) { - $this->handleEnd($conn); - }); - $conn->on('error', function (\Exception $e) use ($conn) { - $this->handleError($e, $conn); - }); - } -} diff --git a/app/Server/Messages/ControlMessage.php b/app/Server/Messages/ControlMessage.php deleted file mode 100644 index c655bd2..0000000 --- a/app/Server/Messages/ControlMessage.php +++ /dev/null @@ -1,66 +0,0 @@ -payload = $payload; - - $this->connection = $connection; - - $this->connectionManager = $connectionManager; - } - - public function respond() - { - $eventName = $this->payload->event; - - if (method_exists($this, $eventName)) { - call_user_func([$this, $eventName], $this->connection, $this->payload->data ?? new stdClass()); - } - } - - protected function authenticate(ConnectionInterface $connection, $data) - { - $connectionInfo = $this->connectionManager->storeConnection($data->host, $data->subdomain, $connection); - - $connection->send(json_encode([ - 'event' => 'authenticated', - 'subdomain' => $connectionInfo->subdomain, - 'client_id' => $connectionInfo->client_id - ])); - - $loop = app(LoopInterface::class); - $timer = $loop->addPeriodicTimer(5, function () use ($connection) { - $connection->send(json_encode([ - 'event' => 'ping' - ])); - }); - } - - protected function registerProxy(ConnectionInterface $connection, $data) - { - $connectionInfo = $this->connectionManager->findConnectionForClientId($data->client_id); - - $connectionInfo->socket->getConnection()->emit('proxy_ready_'.$data->request_id, [ - $connection, - ]); - } -} diff --git a/app/Server/Messages/Message.php b/app/Server/Messages/Message.php deleted file mode 100644 index 6d6983c..0000000 --- a/app/Server/Messages/Message.php +++ /dev/null @@ -1,8 +0,0 @@ - [ - 'Host' => $clientConnection->host, - 'X-Expose-Request-ID' => $requestId, - 'X-Exposed-By' => config('app.name') . ' '. config('app.version'), - 'X-Original-Host' => "{$clientConnection->subdomain}.{$connectionManager->host()}:{$connectionManager->port()}", - ] - ]); - } -} diff --git a/app/Server/Messages/RequestModifiers/RequestModifier.php b/app/Server/Messages/RequestModifiers/RequestModifier.php deleted file mode 100644 index 541b5dc..0000000 --- a/app/Server/Messages/RequestModifiers/RequestModifier.php +++ /dev/null @@ -1,12 +0,0 @@ -connection = $connection; - - $this->connectionManager = $connectionManager; - } - - public function respond() - { - if ($this->connection->hasBufferedAllData()) { - $clientConnection = $this->connectionManager->findConnectionForSubdomain($this->detectSubdomain()); - - if (is_null($clientConnection)) { - $this->connection->send(\GuzzleHttp\Psr7\str(new Response(404, [], 'Not found'))); - $this->connection->close(); - return; - } - - $this->copyDataToClient($clientConnection); - } - } - - protected function detectSubdomain(): ?string - { - $host = $this->connection->getRequest()->getHeader('Host')[0]; - - $domainParts = explode('.', $host); - - return trim($domainParts[0]); - } - - protected function passRequestThroughModifiers(string $requestId, Request $request, Connection $clientConnection): Request - { - foreach ($this->requestModifiers as $requestModifier) { - $request = app($requestModifier)->modify($request, $requestId, $clientConnection, $this->connectionManager); - } - - return $request; - } - - protected function copyDataToClient(Connection $clientConnection) - { - $requestId = uniqid(); - - $request = $this->passRequestThroughModifiers($requestId, $this->connection->getRequest(), $clientConnection); - - $clientConnection->pipeRequestThroughProxy($this->connection, $requestId, $request); - - unset($this->connection->buffer); - } -} diff --git a/app/Server/SubdomainGenerator/RandomSubdomainGenerator.php b/app/Server/SubdomainGenerator/RandomSubdomainGenerator.php new file mode 100644 index 0000000..df9ac77 --- /dev/null +++ b/app/Server/SubdomainGenerator/RandomSubdomainGenerator.php @@ -0,0 +1,14 @@ +=5.5" + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.11" }, "require-dev": { "ext-curl": "*", @@ -494,7 +495,6 @@ "psr/log": "^1.1" }, "suggest": { - "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", @@ -533,7 +533,7 @@ "rest", "web service" ], - "time": "2019-12-23T11:57:10+00:00" + "time": "2020-04-18T10:38:46+00:00" }, { "name": "guzzlehttp/promises", @@ -588,37 +588,40 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.6.1", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + "reference": "3472035ddb363a8452bc6999eeb92a92985879d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3472035ddb363a8452bc6999eeb92a92985879d7", + "reference": "3472035ddb363a8452bc6999eeb92a92985879d7", "shasum": "" }, "require": { - "php": ">=5.4.0", + "php": "^7.2", + "psr/http-factory": "^1.0", "psr/http-message": "~1.0", "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + "ergebnis/composer-normalize": "^2.0", + "http-interop/http-factory-tests": "dev-master", + "phpunit/phpunit": "^8.1" }, "suggest": { - "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -642,6 +645,11 @@ { "name": "Tobias Schultze", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -655,11 +663,11 @@ "uri", "url" ], - "time": "2019-07-01T23:21:34+00:00" + "time": "2020-03-02T13:01:08+00:00" }, { "name": "illuminate/cache", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/cache.git", @@ -710,7 +718,7 @@ }, { "name": "illuminate/config", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/config.git", @@ -754,16 +762,16 @@ }, { "name": "illuminate/console", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/console.git", - "reference": "5bce1dfc670091a812c9b390830e541753b4b651" + "reference": "364648fc102ca0b3a7834934ed5885c11e285f29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/5bce1dfc670091a812c9b390830e541753b4b651", - "reference": "5bce1dfc670091a812c9b390830e541753b4b651", + "url": "https://api.github.com/repos/illuminate/console/zipball/364648fc102ca0b3a7834934ed5885c11e285f29", + "reference": "364648fc102ca0b3a7834934ed5885c11e285f29", "shasum": "" }, "require": { @@ -804,20 +812,20 @@ ], "description": "The Illuminate Console package.", "homepage": "https://laravel.com", - "time": "2020-03-11T12:44:28+00:00" + "time": "2020-04-20T09:32:33+00:00" }, { "name": "illuminate/container", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "10c5802e360595f5f2a8b6afa176b9542851e580" + "reference": "aa8dfe90a3eb31dc760adc911647be5d2e129c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/10c5802e360595f5f2a8b6afa176b9542851e580", - "reference": "10c5802e360595f5f2a8b6afa176b9542851e580", + "url": "https://api.github.com/repos/illuminate/container/zipball/aa8dfe90a3eb31dc760adc911647be5d2e129c8a", + "reference": "aa8dfe90a3eb31dc760adc911647be5d2e129c8a", "shasum": "" }, "require": { @@ -848,11 +856,11 @@ ], "description": "The Illuminate Container package.", "homepage": "https://laravel.com", - "time": "2020-04-06T13:33:36+00:00" + "time": "2020-04-20T14:17:11+00:00" }, { "name": "illuminate/contracts", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", @@ -896,7 +904,7 @@ }, { "name": "illuminate/events", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/events.git", @@ -941,7 +949,7 @@ }, { "name": "illuminate/filesystem", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", @@ -994,16 +1002,16 @@ }, { "name": "illuminate/http", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/http.git", - "reference": "c11e7175b7b751ce8ae5dfac7fbe46d47b6c2f39" + "reference": "05444c7ecfad9e12c42d75f6b9a3dd0adb8b0a57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/c11e7175b7b751ce8ae5dfac7fbe46d47b6c2f39", - "reference": "c11e7175b7b751ce8ae5dfac7fbe46d47b6c2f39", + "url": "https://api.github.com/repos/illuminate/http/zipball/05444c7ecfad9e12c42d75f6b9a3dd0adb8b0a57", + "reference": "05444c7ecfad9e12c42d75f6b9a3dd0adb8b0a57", "shasum": "" }, "require": { @@ -1042,11 +1050,55 @@ ], "description": "The Illuminate Http package.", "homepage": "https://laravel.com", - "time": "2020-04-15T18:37:09+00:00" + "time": "2020-04-20T08:03:18+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v7.7.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "1a7ef33dec9cbb73757f0654ae48d997944a4d3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/1a7ef33dec9cbb73757f0654ae48d997944a4d3f", + "reference": "1a7ef33dec9cbb73757f0654ae48d997944a4d3f", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^7.0", + "illuminate/support": "^7.0", + "php": "^7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "time": "2020-02-25T14:26:37+00:00" }, { "name": "illuminate/session", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/session.git", @@ -1097,16 +1149,16 @@ }, { "name": "illuminate/support", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "b6f64a42377f86b293960e0b7f6920ae59e578d2" + "reference": "7143690b5b718a4a89f8a1ecda79833e75dd138c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/b6f64a42377f86b293960e0b7f6920ae59e578d2", - "reference": "b6f64a42377f86b293960e0b7f6920ae59e578d2", + "url": "https://api.github.com/repos/illuminate/support/zipball/7143690b5b718a4a89f8a1ecda79833e75dd138c", + "reference": "7143690b5b718a4a89f8a1ecda79833e75dd138c", "shasum": "" }, "require": { @@ -1155,20 +1207,20 @@ ], "description": "The Illuminate Support package.", "homepage": "https://laravel.com", - "time": "2020-04-15T19:48:40+00:00" + "time": "2020-04-21T15:58:25+00:00" }, { "name": "illuminate/testing", - "version": "v7.6.2", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/illuminate/testing.git", - "reference": "670d48ce1afd008d6e48b2a77c29691ffd4581a3" + "reference": "5ec43e2778a5454b31b886f2ed4f4b2d291419fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/testing/zipball/670d48ce1afd008d6e48b2a77c29691ffd4581a3", - "reference": "670d48ce1afd008d6e48b2a77c29691ffd4581a3", + "url": "https://api.github.com/repos/illuminate/testing/zipball/5ec43e2778a5454b31b886f2ed4f4b2d291419fd", + "reference": "5ec43e2778a5454b31b886f2ed4f4b2d291419fd", "shasum": "" }, "require": { @@ -1206,7 +1258,7 @@ ], "description": "The Illuminate Testing package.", "homepage": "https://laravel.com", - "time": "2020-04-14T14:05:27+00:00" + "time": "2020-04-16T17:12:54+00:00" }, { "name": "jolicode/jolinotif", @@ -1697,16 +1749,16 @@ }, { "name": "laravel-zero/framework", - "version": "v7.0.0", + "version": "v7.1.0", "source": { "type": "git", "url": "https://github.com/laravel-zero/framework.git", - "reference": "17723324e1f398b4f7791b8c3a24ebbf4761d0ce" + "reference": "f76b8cf12a6ca54ebbb27bab5d64a2db43f3b80c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel-zero/framework/zipball/17723324e1f398b4f7791b8c3a24ebbf4761d0ce", - "reference": "17723324e1f398b4f7791b8c3a24ebbf4761d0ce", + "url": "https://api.github.com/repos/laravel-zero/framework/zipball/f76b8cf12a6ca54ebbb27bab5d64a2db43f3b80c", + "reference": "f76b8cf12a6ca54ebbb27bab5d64a2db43f3b80c", "shasum": "" }, "require": { @@ -1779,20 +1831,20 @@ "framework", "laravel" ], - "time": "2020-03-15T19:14:33+00:00" + "time": "2020-04-17T16:26:47+00:00" }, { "name": "league/flysystem", - "version": "1.0.66", + "version": "1.0.67", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5b1f36c75c4bdde981294c2a0ebdb437ee6f275e", + "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e", "shasum": "" }, "require": { @@ -1863,7 +1915,7 @@ "sftp", "storage" ], - "time": "2020-03-17T18:58:12+00:00" + "time": "2020-04-16T13:21:26+00:00" }, { "name": "namshi/cuzzle", @@ -2235,6 +2287,68 @@ ], "time": "2020-01-15T13:26:31+00:00" }, + { + "name": "nyholm/psr7", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/55ff6b76573f5b242554c9775792bd59fb52e11c", + "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c", + "shasum": "" + }, + "require": { + "php": "^7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "dev-master", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "time": "2019-09-05T13:24:16+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.99", @@ -2280,6 +2394,56 @@ ], "time": "2018-07-02T15:55:56+00:00" }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "time": "2015-12-19T14:08:53+00:00" + }, { "name": "phpoption/phpoption", "version": "1.7.3", @@ -2335,6 +2499,52 @@ ], "time": "2020-03-21T18:07:53+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "psr/container", "version": "1.0.0", @@ -2430,6 +2640,58 @@ ], "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -2702,6 +2964,55 @@ ], "time": "2020-02-21T04:36:14+00:00" }, + { + "name": "ratchet/pawl", + "version": "v0.3.4", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Pawl.git", + "reference": "3a7d5b78e0deaec82f42513a4a3193a8eb12feb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/3a7d5b78e0deaec82f42513a4a3193a8eb12feb1", + "reference": "3a7d5b78e0deaec82f42513a4a3193a8eb12feb1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0", + "php": ">=5.4", + "ratchet/rfc6455": "^0.2.3", + "react/socket": "^1.0 || ^0.8 || ^0.7" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "reactivex/rxphp": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\Client\\": "src" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Asynchronous WebSocket client", + "keywords": [ + "Ratchet", + "async", + "client", + "websocket", + "websocket client" + ], + "time": "2019-01-14T14:09:36+00:00" + }, { "name": "ratchet/rfc6455", "version": "v0.2.6", @@ -3279,6 +3590,143 @@ ], "time": "2020-01-24T09:39:24+00:00" }, + { + "name": "symfony/cache", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "7c229da093cb0c630e5d16b99fd253e20f979ac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/7c229da093cb0c630e5d16b99fd253e20f979ac2", + "reference": "7c229da093cb0c630e5d16b99fd253e20f979ac2", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2020-03-27T16:56:45+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-11-18T17:27:11+00:00" + }, { "name": "symfony/console", "version": "v5.0.7", @@ -3538,6 +3986,57 @@ ], "time": "2019-11-18T17:27:11+00:00" }, + { + "name": "symfony/expression-language", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "00e044885469d193c3b8dfa62030cd4525576d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/00e044885469d193c3b8dfa62030cd4525576d4e", + "reference": "00e044885469d193c3b8dfa62030cd4525576d4e", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "symfony/cache": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ExpressionLanguage Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:56:45+00:00" + }, { "name": "symfony/finder", "version": "v5.0.7", @@ -4548,6 +5047,66 @@ ], "time": "2020-03-27T16:56:45+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v5.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "ffd29a70370e466343e33154b5df197a07a13afa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/ffd29a70370e466343e33154b5df197a07a13afa", + "reference": "ffd29a70370e466343e33154b5df197a07a13afa", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2020-03-27T16:56:45+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v4.1.4", @@ -6188,16 +6747,16 @@ }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", "shasum": "" }, "require": { @@ -6205,7 +6764,7 @@ "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -6232,12 +6791,21 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-04-18T12:12:48+00:00" + } + ], + "aliases": [ + { + "alias": "1.6.1", + "alias_normalized": "1.6.1.0", + "version": "9999999-dev", + "package": "guzzlehttp/psr7" } ], - "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "guzzlehttp/psr7": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": {