1
0
Fork 0
mirror of synced 2024-09-28 15:31:43 +12:00

Improve headers validator

This commit is contained in:
Matej Bačo 2024-08-20 09:19:31 +00:00
parent 22b054453c
commit bbefdc3405
4 changed files with 55 additions and 45 deletions

View file

@ -10,7 +10,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Run Linter
- name: Run Benchmark
run: |
docker run --rm -v $PWD:/app composer sh -c \
"composer install --profile --ignore-platform-reqs && git config --global --add safe.directory /app && composer bench -- --progress=plain"

View file

@ -11,11 +11,8 @@ use Utopia\Validator;
*/
class Headers extends Validator
{
protected bool $allowEmpty;
public function __construct(bool $allowEmpty = true)
public function __construct(protected bool $allowEmpty = true, protected int $maxKeys = 100, protected int $maxSize = 16384)
{
$this->allowEmpty = $allowEmpty;
}
/**
@ -27,7 +24,7 @@ class Headers extends Validator
*/
public function getDescription(): string
{
return 'Invalid header format. Header keys can only contain alphanumeric characters, underscores, and hyphens. Header keys cannot start with "x-appwrite-" prefix.';
return 'Invalid headers: Alphanumeric characters or hyphens only, cannot start with "x-appwrite", maximum ' . $this->maxKeys . ' keys, and total size ' . $this->maxSize . '.';
}
/**
@ -47,34 +44,41 @@ class Headers extends Validator
return false;
}
if (\is_array($value)) {
foreach ($value as $key => $val) {
$length = \strlen($key);
// Reject non-string keys
if (!\is_string($key) || $length === 0) {
return false;
}
if(\count($value) > $this->maxKeys) {
return false;
}
// Check first and last character
if (!ctype_alnum($key[0]) || !ctype_alnum($key[$length - 1])) {
return false;
}
$size = 0;
foreach ($value as $key => $val) {
$length = \strlen($key);
// Reject non-string keys
if (!\is_string($key) || $length === 0) {
return false;
}
// Check middle characters
for ($i = 1; $i < $length - 1; $i++) {
if (!ctype_alnum($key[$i]) && $key[$i] !== '-') {
return false;
}
}
$size += $length + \strlen($val);
if($size >= $this->maxSize) {
return false;
}
// Check for x-appwrite- prefix
if (str_starts_with($key, 'x-appwrite-')) {
// Check first and last character
if (!ctype_alnum($key[0]) || !ctype_alnum($key[$length - 1])) {
return false;
}
// Check middle characters
for ($i = 1; $i < $length - 1; $i++) {
if (!ctype_alnum($key[$i]) && $key[$i] !== '-') {
return false;
}
}
return true;
// Check for x-appwrite- prefix
if (str_starts_with($key, 'x-appwrite-')) {
return false;
}
}
return false;
return true;
}
/**

View file

@ -28,38 +28,26 @@ final class HeadersBench
for($i = 0; $i < 10; $i++) {
$value[bin2hex(random_bytes(8))] = bin2hex(random_bytes(8));
}
yield 'xxs' => [ 'value' => $value ];
yield 'items_10-size_320' => [ 'value' => $value ];
$value = [];
for($i = 0; $i < 100; $i++) {
$value[bin2hex(random_bytes(8))] = bin2hex(random_bytes(8));
}
yield 'xs' => [ 'value' => $value ];
yield 'items_100-size_3200' => [ 'value' => $value ];
$value = [];
for($i = 0; $i < 1000; $i++) {
$value[bin2hex(random_bytes(8))] = bin2hex(random_bytes(8));
for($i = 0; $i < 100; $i++) {
$value[bin2hex(random_bytes(64))] = bin2hex(random_bytes(64));
}
yield 'sm' => [ 'value' => $value ];
$value = [];
for($i = 0; $i < 10000; $i++) {
$value[bin2hex(random_bytes(16))] = bin2hex(random_bytes(16));
}
yield 'md' => [ 'value' => $value ];
$value = [];
for($i = 0; $i < 100000; $i++) {
$value[bin2hex(random_bytes(16))] = bin2hex(random_bytes(16));
}
yield 'lg' => [ 'value' => $value ];
yield 'items_100-size_25600' => [ 'value' => $value ];
}
#[BeforeMethods('prepare')]
#[AfterMethods('tearDown')]
#[ParamProviders('providers')]
#[Iterations(50)]
#[Assert('mode(variant.time.avg) < 500 ms')]
#[Assert('mode(variant.time.avg) < 1 ms')]
public function benchHeadersValidator(array $data): void
{
$assertion = $this->validator->isValid($data['value']);

View file

@ -104,5 +104,23 @@ class HeadersTest extends TestCase
$headers = 'string';
$this->assertFalse($this->object->isValid($headers));
$headers = [ ];
$this->assertTrue($this->object->isValid($headers));
$headers = [];
for($i = 0; $i < 100; $i++) {
$headers['key-' . $i] = 'value_' . $i;
}
$this->assertTrue($this->object->isValid($headers));
$headers['key-oversized'] = bin2hex(random_bytes(100000));
$this->assertFalse($this->object->isValid($headers));
unset($headers['key-oversized']);
$this->assertTrue($this->object->isValid($headers));
$headers['key-101'] = 'value_101';
$this->assertFalse($this->object->isValid($headers));
}
}