mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-13 21:45:55 +00:00
wip
This commit is contained in:
147
app/Client/Http/HttpClient.php
Normal file
147
app/Client/Http/HttpClient.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Client\Http;
|
||||
|
||||
use App\Client\Http\Modifiers\CheckBasicAuthentication;
|
||||
use App\Logger\RequestLogger;
|
||||
use Clue\React\Buzz\Browser;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Http\Request;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Ratchet\Client\WebSocket;
|
||||
use Ratchet\RFC6455\Messaging\Frame;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Socket\Connector;
|
||||
use function GuzzleHttp\Psr7\parse_request;
|
||||
use function GuzzleHttp\Psr7\str;
|
||||
|
||||
class HttpClient
|
||||
{
|
||||
/** @var LoopInterface */
|
||||
protected $loop;
|
||||
|
||||
/** @var RequestLogger */
|
||||
protected $logger;
|
||||
|
||||
/** @var Request */
|
||||
protected $request;
|
||||
|
||||
/** @var array */
|
||||
protected $modifiers = [
|
||||
CheckBasicAuthentication::class,
|
||||
];
|
||||
|
||||
public function __construct(LoopInterface $loop, RequestLogger $logger)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
98
app/Client/Http/Modifiers/CheckBasicAuthentication.php
Normal file
98
app/Client/Http/Modifiers/CheckBasicAuthentication.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Client\Http\Modifiers;
|
||||
|
||||
use App\Client\Configuration;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Ratchet\Client\WebSocket;
|
||||
use function GuzzleHttp\Psr7\str;
|
||||
|
||||
class CheckBasicAuthentication
|
||||
{
|
||||
/** @var Configuration */
|
||||
protected $configuration;
|
||||
|
||||
public function __construct(Configuration $configuration)
|
||||
{
|
||||
$this->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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user