initial prototype based on nat-as-server
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
tom.hempel
2026-06-29 12:39:55 +02:00
commit f1caa9e681
148 changed files with 34905 additions and 0 deletions

61
lib/text_sanitize.php Normal file
View File

@ -0,0 +1,61 @@
<?php
/**
* Sanitize user-entered free text before storage (defense in depth; queries use prepared statements).
*/
function sanitize_free_text(mixed $value, int $maxLength = 2000): string {
if (!is_string($value) && !is_numeric($value)) {
return '';
}
$s = trim((string) $value);
if ($s === '') {
return '';
}
$maxLength = max(1, min($maxLength, 10000));
$s = mb_substr($s, 0, $maxLength);
// Control characters (keep tab/newline for multiline answers).
$s = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $s) ?? '';
$s = str_replace(["\0", '--', '/*', '*/', ';'], '', $s);
$patterns = [
'/\bunion\s+select\b/i',
'/\bselect\s+.+\s+from\b/i',
'/\binsert\s+into\b/i',
'/\bupdate\s+.+\s+set\b/i',
'/\bdelete\s+from\b/i',
'/\bdrop\s+(table|database)\b/i',
'/\bexec(\s|\()/i',
'/\bxp_\w+/i',
'/\bor\s+1\s*=\s*1\b/i',
'/\band\s+1\s*=\s*1\b/i',
"/'\s*or\s+'/i",
'/"\s*or\s+"/i',
'/\bchar\s*\(/i',
'/\bconcat\s*\(/i',
'/\bsleep\s*\(/i',
'/\bbenchmark\s*\(/i',
];
foreach ($patterns as $pattern) {
$s = preg_replace($pattern, '', $s) ?? $s;
}
return trim(preg_replace('/\s{2,}/u', ' ', $s) ?? '');
}
/**
* Returns sanitized text or calls json_error when nothing safe remains.
*/
function require_safe_free_text(mixed $value, string $fieldName, int $maxLength = 2000): string {
$raw = is_string($value) || is_numeric($value) ? trim((string) $value) : '';
if ($raw === '') {
json_error('INVALID_FIELD', "$fieldName is required", 400);
}
$clean = sanitize_free_text($raw, $maxLength);
if ($clean === '') {
json_error('INVALID_FIELD', "$fieldName contains disallowed content", 400);
}
return $clean;
}