Skip to content

Commit

Permalink
service timeouts + no signals + no exit codes
Browse files Browse the repository at this point in the history
  • Loading branch information
medilies committed Aug 12, 2024
1 parent 09b0411 commit 9401559
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 102 deletions.
71 changes: 14 additions & 57 deletions src/Dompurify/DompurifyService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Medilies\Xssless\ConfigInterface;
use Medilies\Xssless\HasSetupInterface;
use Medilies\Xssless\ServiceInterface;
use Medilies\Xssless\XsslessException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

Expand Down Expand Up @@ -58,15 +57,24 @@ public function start(): static
$this->config->port,
]);

$this->serviceProcess->setIdleTimeout($this->config->startupTimeoutMs / 1000);

$this->serviceProcess->start();

// ? is there a possibility to miss output before running the waitUntil
$this->serviceProcess->waitUntil(function (string $type, string $buffer) {
// ? timeout
// ! ensure service always returns output
return strlen($buffer) > 5;
if ($type === Process::ERR) {
throw new ProcessFailedException($this->serviceProcess);
}

// Must output when service is listening
return strlen($buffer) > 4;
});

if (! $this->serviceProcess->isRunning()) {
$this->serviceProcess->setIdleTimeout(null);

// ? rm check
if (! $this->isRunning()) {
throw new ProcessFailedException($this->serviceProcess);
}

Expand All @@ -75,9 +83,7 @@ public function start(): static

public function stop(): static
{
if ($this->isRunning()) {
$this->serviceProcess->stop();
}
$this->serviceProcess->stop();

return $this;
}
Expand All @@ -96,53 +102,4 @@ public function getIncrementalErrorOutput(): string
{
return $this->serviceProcess->getIncrementalErrorOutput();
}

public function throwIfFailedOnTerm(): void
{
if ($this->serviceProcess->isRunning()) {
// ? stop it
throw new XsslessException('The service is still running');
}

// TODO: fix windows check
// https://github.com/medilies/xssless/actions/runs/10288495452/job/28474063301#step:7:26
if ($this->isSigTerm() || $this->isWindows()) {
return;
}

throw new ProcessFailedException($this->serviceProcess);
}

// ? interface
public function waitForTermination(int $timeout): void
{
$elapsed = 0;
$sleepInterval = 100; // Sleep for 100 milliseconds

while ($this->serviceProcess->isRunning() && $elapsed < $timeout) {
usleep($sleepInterval * 1000);
$elapsed += $sleepInterval;
}

if ($this->serviceProcess->isRunning()) {
throw new XsslessException('Process did not terminate within the given timeout');
}
}

// ========================================================================

private function isWindows(): bool
{
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}

private function isSigTerm(): bool
{
return $this->serviceProcess->getTermSignal() === 15;
}

// private function isSigHup(): bool
// {
// return $this->serviceProcess->getTermSignal() === 1;
// }
}
1 change: 1 addition & 0 deletions src/Dompurify/DompurifyServiceConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public function __construct(
public string $host = '127.0.0.1',
public int $port = 63000,
public ?string $binary = null,
public int $startupTimeoutMs = 6000,
) {
$this->class = DompurifyService::class;
}
Expand Down
2 changes: 0 additions & 2 deletions src/ServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ public function isRunning(): bool;
public function getIncrementalOutput(): string;

public function getIncrementalErrorOutput(): string;

public function throwIfFailedOnTerm(): void;
}
3 changes: 1 addition & 2 deletions src/laravel/Commands/StartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function handle(): void
exit;
};

// ? Is this necessary
pcntl_signal(SIGTERM, $terminate);
pcntl_signal(SIGINT, $terminate);

Expand All @@ -43,7 +44,5 @@ public function handle(): void
// Sleep for a short period to avoid busy-waiting
usleep(100_000);
}

$service->throwIfFailedOnTerm();
}
}
34 changes: 17 additions & 17 deletions tests/Dompurify/DompurifyCliTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@
expect(fn () => $cleaner->exec('foo'))->toThrow(ProcessFailedException::class);
});

it('throws when cannot find binary file', function () {
$cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
binary: __DIR__.'/js-mocks/x.js',
));

expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
});

it('throws when cannot locate temp folder', function () {
$cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
tempFolder: __DIR__.'/x',
));

expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
});

test('setup()', function () {
$cleaner = (new Xssless)->using(new DompurifyCliConfig);

Expand Down Expand Up @@ -44,20 +60,4 @@
));

expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
})->depends('setup()');

it('throws when cannot find binary file', function () {
$cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
binary: __DIR__.'/js-mocks/x.js',
));

expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
})->depends('setup()');

it('throws when cannot locate temp folder', function () {
$cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
tempFolder: __DIR__.'/x',
));

expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
})->depends('setup()');
});
62 changes: 38 additions & 24 deletions tests/Dompurify/DompurifyServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@
use Medilies\Xssless\Dompurify\DompurifyServiceConfig;
use Medilies\Xssless\Xssless;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

it('throws on bad node path', function () {
$service = (new DompurifyService)->configure(new DompurifyServiceConfig(
node: 'nodeZz',
));

expect(fn () => $service->start())->toThrow(ProcessFailedException::class);
});

it('throws when cannot find binary file', function () {
$cleaner = (new DompurifyService)->configure(new DompurifyServiceConfig(
binary: __DIR__.'/js-mocks/x.js',
));

expect(fn () => $cleaner->start('foo'))->toThrow(ProcessFailedException::class);
});

it('throws on start() timeout', function () {
$service = (new DompurifyService)->configure(new DompurifyServiceConfig(
binary: __DIR__.'/js-mocks/service-start-timeout.js',
startupTimeoutMs: 50,
));

expect(fn () => $service->start())->toThrow(ProcessTimedOutException::class);
});

it('throws on bad host', function () {
$cleaner = (new DompurifyService)->configure(new DompurifyServiceConfig(
host: 'a.b.c.example.com',
));

$dirty = '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">';

expect(fn () => $cleaner->send($dirty))->toThrow(ConnectException::class);
});

test('setup()', function () {
$cleaner = (new DompurifyService)->configure(new DompurifyServiceConfig);
Expand All @@ -21,7 +57,7 @@

$clean = $cleaner->send($dirty);

$cleaner->stop()->throwIfFailedOnTerm();
$cleaner->stop();

expect($clean)->toBe(str_repeat('*/', 34 * 1000).'<img>"&gt;');
})->depends('setup()');
Expand All @@ -39,29 +75,7 @@

$clean = $cleaner->clean($dirty);

$service->stop()->throwIfFailedOnTerm();
$service->stop();

expect($clean)->toBe('<img>"&gt;');
})->depends('setup()');

it('throws on bad host', function () {
$cleaner = (new DompurifyService)->configure(new DompurifyServiceConfig(
host: 'a.b.c.example.com',
));

$dirty = '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">';

expect(fn () => $cleaner->send($dirty))->toThrow(ConnectException::class);
});

it('throws on bad node path', function () {
$service = (new DompurifyService)->configure(new DompurifyServiceConfig(
node: 'nodeZz',
));

expect(fn () => $service->start())->toThrow(ProcessFailedException::class);

// $service->waitForTermination(2000);
// expect($service->serviceProcess->getExitCode())->toBe(127);
// TODO: fix https://github.com/medilies/xssless/actions/runs/10284119857/job/28459470742#step:7:28
});
4 changes: 4 additions & 0 deletions tests/Dompurify/js-mocks/service-start-timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const start = Date.now();
while (Date.now() - start < 500) {

}

0 comments on commit 9401559

Please sign in to comment.