- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
195 lines
6.5 KiB
PHP
195 lines
6.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception\Http;
|
|
|
|
use App\Framework\Exception\Core\HttpErrorCode;
|
|
use App\Framework\Exception\ExceptionContext;
|
|
use App\Framework\Exception\FrameworkException;
|
|
|
|
/**
|
|
* Ausnahme für fehlerhaft formatiertes JSON
|
|
*
|
|
* Verwendet OWASP-konforme Nachrichten für JSON-Parsing-Fehler
|
|
*/
|
|
final class MalformedJsonException extends FrameworkException
|
|
{
|
|
/**
|
|
* @param string $clientIp Client-IP für Security-Tracking
|
|
* @param string $jsonError JSON-Parser-Fehlermeldung
|
|
* @param int $jsonErrorCode JSON-Error-Code
|
|
* @param string $jsonData Ursprüngliche JSON-Daten (gekürzt)
|
|
* @param \Throwable|null $previous Vorherige Ausnahme
|
|
*/
|
|
public function __construct(
|
|
public readonly string $clientIp,
|
|
public readonly string $jsonError,
|
|
public readonly int $jsonErrorCode = JSON_ERROR_SYNTAX,
|
|
public readonly string $jsonData = '',
|
|
?\Throwable $previous = null
|
|
) {
|
|
// OWASP-konforme Nachricht mit Platzhaltern
|
|
$message = "Client {$this->clientIp} sent malformed JSON: {$this->jsonError}";
|
|
|
|
$context = ExceptionContext::forOperation('http.json_parsing', 'Http')
|
|
->withData([
|
|
'client_ip' => $this->clientIp,
|
|
'json_error' => $this->jsonError,
|
|
'json_error_code' => $this->jsonErrorCode,
|
|
'json_data_length' => strlen($this->jsonData),
|
|
'event_identifier' => "http_malformed_json:{$this->clientIp}",
|
|
'category' => 'http_validation',
|
|
'requires_alert' => false,
|
|
])
|
|
->withDebug([
|
|
'json_data_sample' => substr($this->jsonData, 0, 100), // Erste 100 Zeichen für Debug
|
|
])
|
|
->withMetadata([
|
|
'security_event' => true,
|
|
'owasp_compliant' => true,
|
|
'log_level' => 'INFO',
|
|
'http_error' => true,
|
|
]);
|
|
|
|
parent::__construct(
|
|
message: $message,
|
|
context: $context,
|
|
code: 400, // Bad Request
|
|
previous: $previous,
|
|
errorCode: HttpErrorCode::BAD_REQUEST
|
|
);
|
|
}
|
|
|
|
// === Factory Methods für verschiedene JSON-Fehler ===
|
|
|
|
public static function syntaxError(string $clientIp, string $jsonData = ''): self
|
|
{
|
|
return new self($clientIp, 'Syntax error', JSON_ERROR_SYNTAX, $jsonData);
|
|
}
|
|
|
|
public static function depthExceeded(string $clientIp, string $jsonData = ''): self
|
|
{
|
|
return new self($clientIp, 'Maximum stack depth exceeded', JSON_ERROR_DEPTH, $jsonData);
|
|
}
|
|
|
|
public static function ctrlCharError(string $clientIp, string $jsonData = ''): self
|
|
{
|
|
return new self($clientIp, 'Unexpected control character found', JSON_ERROR_CTRL_CHAR, $jsonData);
|
|
}
|
|
|
|
public static function utf8Error(string $clientIp, string $jsonData = ''): self
|
|
{
|
|
return new self($clientIp, 'Malformed UTF-8 characters', JSON_ERROR_UTF8, $jsonData);
|
|
}
|
|
|
|
public static function fromJsonLastError(string $clientIp, string $jsonData = ''): self
|
|
{
|
|
$error = json_last_error_msg();
|
|
$code = json_last_error();
|
|
|
|
return new self($clientIp, $error, $code, $jsonData);
|
|
}
|
|
|
|
/**
|
|
* Gibt OWASP-konforme Event-Daten zurück
|
|
*/
|
|
public function getSecurityEventData(): array
|
|
{
|
|
return [
|
|
'event_identifier' => "http_malformed_json:{$this->clientIp}",
|
|
'description' => "Client {$this->clientIp} sent malformed JSON: {$this->jsonError}",
|
|
'category' => 'http_validation',
|
|
'log_level' => 'INFO',
|
|
'requires_alert' => false,
|
|
'client_ip' => $this->clientIp,
|
|
'json_error' => $this->jsonError,
|
|
'json_error_code' => $this->jsonErrorCode,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Gibt benutzerfreundliche Fehlermeldung zurück
|
|
*/
|
|
public function getUserMessage(): string
|
|
{
|
|
return match ($this->jsonErrorCode) {
|
|
JSON_ERROR_SYNTAX => 'Invalid JSON format. Please check your JSON syntax.',
|
|
JSON_ERROR_DEPTH => 'JSON structure too complex. Please reduce nesting depth.',
|
|
JSON_ERROR_CTRL_CHAR => 'Invalid characters in JSON. Please check for control characters.',
|
|
JSON_ERROR_UTF8 => 'Invalid UTF-8 encoding in JSON. Please check character encoding.',
|
|
default => 'Invalid JSON format. Please check your request body.',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gibt detaillierte Fehlerbeschreibung für Entwickler zurück
|
|
*/
|
|
public function getDeveloperMessage(): string
|
|
{
|
|
$position = $this->findErrorPosition();
|
|
$positionInfo = $position ? " at position {$position}" : '';
|
|
|
|
return "JSON parsing failed: {$this->jsonError}{$positionInfo}";
|
|
}
|
|
|
|
/**
|
|
* Versucht die Position des JSON-Fehlers zu finden
|
|
*/
|
|
private function findErrorPosition(): ?int
|
|
{
|
|
if (empty($this->jsonData)) {
|
|
return null;
|
|
}
|
|
|
|
// Vereinfachte Positionsbestimmung
|
|
// In einer echten Implementierung könnte hier ein detaillierterer Parser verwendet werden
|
|
$testData = $this->jsonData;
|
|
$position = 0;
|
|
|
|
while ($position < strlen($testData)) {
|
|
$substr = substr($testData, 0, $position + 1);
|
|
json_decode($substr);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
return $position;
|
|
}
|
|
|
|
$position++;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gibt JSON-Error-Code als String zurück
|
|
*/
|
|
public function getJsonErrorName(): string
|
|
{
|
|
return match ($this->jsonErrorCode) {
|
|
JSON_ERROR_NONE => 'JSON_ERROR_NONE',
|
|
JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH',
|
|
JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH',
|
|
JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR',
|
|
JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX',
|
|
JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8',
|
|
JSON_ERROR_RECURSION => 'JSON_ERROR_RECURSION',
|
|
JSON_ERROR_INF_OR_NAN => 'JSON_ERROR_INF_OR_NAN',
|
|
JSON_ERROR_UNSUPPORTED_TYPE => 'JSON_ERROR_UNSUPPORTED_TYPE',
|
|
default => 'UNKNOWN_JSON_ERROR',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Prüft ob der Fehler ein häufiger Entwicklerfehler ist
|
|
*/
|
|
public function isCommonDeveloperError(): bool
|
|
{
|
|
return in_array($this->jsonErrorCode, [
|
|
JSON_ERROR_SYNTAX,
|
|
JSON_ERROR_CTRL_CHAR,
|
|
JSON_ERROR_UTF8,
|
|
]);
|
|
}
|
|
}
|