Skip to content

Commit

Permalink
Update elephant.io (#3)
Browse files Browse the repository at this point in the history
* Remove polling fallback

* Update elephantio

* Update phpstan

* Replace deprecated method for elephant.io client

* Replace deprecated method

* Update elephant.io client

* Update elephantio

* Fix update

* Update elephantio to latest version and use 'wait' instead of 'drain'

* Update elephantio to latest version
  • Loading branch information
Gared authored Nov 20, 2024
1 parent ab8261b commit 63f1758
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 221 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"ext-json": "*",
"guzzlehttp/guzzle": "^7.8",
"symfony/console": "^6.4|^7.0",
"elephantio/elephant.io": "4.3.*"
"elephantio/elephant.io": "^4.8"
},
"require-dev": {
"phpstan/phpstan": "^1.10"
Expand Down
30 changes: 16 additions & 14 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

210 changes: 5 additions & 205 deletions src/Service/ScannerService.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,6 @@ private function scanPad(ScannerServiceCallbackInterface $callback): void
$this->doSocketWebsocket($socketIoVersion, $cookieString, $callback, $token);
} catch (Exception $e) {
$callback->onScanPadException($e);

try {
if ($socketIoVersion === ElephantClient::CLIENT_4X) {
$this->doSocketPolling4($cookies, $token, $callback);
return;
}

$this->doSocketPolling($socketIoVersion, $cookies, $token, $callback);
} catch (Exception $e) {
$callback->onScanPadException($e);
}
}
}

Expand Down Expand Up @@ -356,191 +345,6 @@ private function scanHealth(ScannerServiceCallbackInterface $callback): void
}
}

private function doSocketPolling(
int $socketIoVersion,
CookieJar $cookies,
string $token,
ScannerServiceCallbackInterface $callback
): void {
$engine = ElephantClient::engine($socketIoVersion, '');

$queryParameters = [
'padId' => $this->padId,
'EIO' => $engine->getOptions()['version'],
'transport' => 'polling',
't' => Yeast::yeast(),
'b64' => 1,
];

$response = $this->client->get($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
]);
$body = (string)$response->getBody();
if ($body === 'Welcome to socket.io.') {
$this->packageVersion = '1.4.0';
throw new Exception('Socket.io 1 not supported');
}
$curlyBracketPos = strpos($body, '{');
if ($curlyBracketPos === false) {
throw new Exception('No JSON response: ' . $body);
}
$body = substr($body, $curlyBracketPos);
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
$sid = $data['sid'];

$queryParameters['sid'] = $sid;
$queryParameters['t'] = Yeast::yeast();

$response = $this->client->get($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
]);
$body = (string)$response->getBody();
if ($body !== '2:40') {
throw new Exception('Invalid response: ' . $body);
}

$postData = json_encode([
'message',
[
'component' => 'pad',
'type' => 'CLIENT_READY',
'padId' => $this->padId,
'sessionID' => 'null',
'token' => $token,
'password' => null,
'protocolVersion' => 2,
]
]);

$queryParameters['t'] = Yeast::yeast();
$response = $this->client->post($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'body' => (mb_strlen($postData) + 2) . ':42' . $postData,
'cookies' => $cookies,
]);
$body = (string)$response->getBody();
if ($body !== 'ok') {
throw new Exception('Invalid response: ' . $body);
}

$queryParameters['t'] = Yeast::yeast();
$response = $this->client->get($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
]);
$this->handleClientVarsResponse($response, $callback);
}

private function doSocketPolling4(
CookieJar $cookies,
string $token,
ScannerServiceCallbackInterface $callback
): void {
$queryParameters = [
'padId' => $this->padId,
'EIO' => 4,
'transport' => 'polling',
't' => Yeast::yeast(),
'b64' => 1,
];

$response = $this->client->get($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
]);
$body = (string)$response->getBody();
$curlyBracketPos = strpos($body, '{');
if ($curlyBracketPos === false) {
throw new Exception('No JSON response: ' . $body);
}
$body = substr($body, $curlyBracketPos);
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
$sid = $data['sid'];

$queryParameters['sid'] = $sid;
$queryParameters['t'] = Yeast::yeast();

$response = $this->client->post($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
'body' => '40',
]);
$body = (string)$response->getBody();
if ($body !== 'ok') {
throw new Exception('Invalid response: ' . $body);
}

$queryParameters['t'] = Yeast::yeast();

$response = $this->client->get($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
]);
$body = (string)$response->getBody();

if (str_starts_with($body, '40') === false) {
throw new Exception('Invalid response: ' . $body);
}

$postData = json_encode([
'message',
[
'component' => 'pad',
'type' => 'CLIENT_READY',
'padId' => $this->padId,
'sessionID' => 'null',
'token' => $token,
'password' => null,
'protocolVersion' => 2,
]
]);

$queryParameters['t'] = Yeast::yeast();
$response = $this->client->post($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'body' => '42' . $postData,
'cookies' => $cookies,
]);
$body = (string)$response->getBody();
if ($body !== 'ok') {
throw new Exception('Invalid response: ' . $body);
}

$queryParameters['t'] = Yeast::yeast();
$response = $this->client->get($this->baseUrl . 'socket.io/', [
'query' => $queryParameters,
'cookies' => $cookies,
]);
$this->handleClientVarsResponse($response, $callback);
}

private function handleClientVarsResponse(
ResponseInterface $response,
ScannerServiceCallbackInterface $callback,
): void
{
$body = (string)$response->getBody();
$body = substr($body, strpos($body, '['));
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
$data = $data[1];
$accessStatus = $data['accessStatus'] ?? null;
if ($accessStatus === 'deny') {
$callback->onScanPadException(new EtherpadServiceNotPublicException('Pads are not publicly accessible'));
return;
}

$version = $data['data']['plugins']['plugins']['ep_etherpad-lite']['package']['version'];
$onlyPlugins = $data['data']['plugins']['plugins'];
unset($onlyPlugins['ep_etherpad-lite']);

$this->packageVersion = $version;
$callback->onClientVars($version, $data);
$callback->onScanPluginsList($onlyPlugins);
$callback->onScanPadSuccess();
}

private function doSocketWebsocket(
int $socketIoVersion,
string $cookieString,
Expand All @@ -560,7 +364,7 @@ private function doSocketWebsocket(
]
]), $callback->getConsoleLogger());

$socketIoClient->initialize();
$socketIoClient->connect();
$socketIoClient->of('/');
$socketIoClient->emit('message', [
'component' => 'pad',
Expand All @@ -572,12 +376,8 @@ private function doSocketWebsocket(
'protocolVersion' => 2,
]);

$expirationTime = microtime(true) + 2;

while (microtime(true) < $expirationTime) {
usleep(10000);
$result = $socketIoClient->drain();
if ($result !== null && is_array($result->data)) {
while ($result = $socketIoClient->wait('message', 2)) {
if (is_array($result->data)) {
$accessStatus = $result->data['accessStatus'] ?? null;
if ($accessStatus === 'deny') {
$callback->onScanPadException(new EtherpadServiceNotPublicException('Pads are not publicly accessible'));
Expand All @@ -600,11 +400,11 @@ private function doSocketWebsocket(
}
}

$socketIoClient->close();
$socketIoClient->disconnect();
}

public function getBaseUrl(): string
{
return $this->baseUrl;
}
}
}
2 changes: 1 addition & 1 deletion tests/e2e/fixture/reverse_proxy_no_websocket.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[debug] Receive: HTTP/1.1 400 Bad Request
[debug] Stream receive: HTTP/1.1 400 Bad Request
[INFO] Package version: 1.8.17

0 comments on commit 63f1758

Please sign in to comment.