Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
194
src/Framework/Exception/Http/MalformedJsonException.php
Normal file
194
src/Framework/Exception/Http/MalformedJsonException.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Exception\Http;
|
||||
|
||||
use App\Framework\Exception\ErrorCode;
|
||||
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: ErrorCode::HTTP_MALFORMED_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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user